To test an i18n language selection Vue component I want to check if the logic works that decides what language to set. To write a Cypress test to see if the language stored in localeStorage is chosen over the lang parameter provided through an URL, I need to get the URL parameter, how do I check this?
code example:
LocaleSwitcher.js
<script setup>
import { getCurrentInstance } from 'vue'
import { useBusinessInfoStore } from '../../stores/businessInfo.js'
const { proxy } = getCurrentInstance()
const businessInfoStore = useBusinessInfoStore ()
function getAvailableLocales () {
//put languages available for current app online
return businessInfoStore.availableLanguages.filter(value => proxy.$i18n.availableLocales.includes(value));
}
function setLocale (appLocale) {
let appLanguage
if (appLocale) {
appLanguage = appLocale
} else if (typeof localStorage.currentLanguage !== 'undefined' && getAvailableLocales().includes(localStorage.currentLanguage)) {
//if it has a local language stored use this one
appLanguage = localStorage.currentLanguage
} else {
//get value of url param 'lang'
const urlLang = (new URL(location.href).searchParams.get('lang') || '').toLowerCase()
//get browser lang or return default lang
const userAgentLang = ((navigator.language || navigator.languages[0]).split('-')[0] || import.meta.env.VITE_DEFAULT_LANGUAGE).toLowerCase()
//if there is a selected lang and it is 2 char long
if (urlLang !== null && urlLang.length === 2) {
if (getAvailableLocales().includes(urlLang)) {
appLanguage = urlLang
} else if (getAvailableLocales().includes(userAgentLang)) {
appLanguage = userAgentLang
}
//if no language is set or available use the standaard
} else if (getAvailableLocales().includes(userAgentLang)) {
appLanguage = userAgentLang
} else {
//if no language is set or available use the standaard
appLanguage = import.meta.env.VITE_DEFAULT_LANGUAGE
}
}
//set the language and store it
proxy.$i18n.locale = appLanguage
localStorage.currentLanguage = appLanguage
}
setLocale()
</script>
<template>
<div data-testid="localeSwitcher">
<span v-if="getAvailableLocales().length > 1">
<span v-for="(locale, i) in getAvailableLocales()" :key="`locale-${locale}`">
<span v-if="i != 0" :class="'has-text-' + businessInfoStore.titleTextColor"> | </span>
<!-- :data-current-locale is for Cypress testing purpuses -->
<a @click="setLocale(locale)" :data-testid="locale.toUpperCase()" :data-current-locale="$i18n.locale === locale ? 'true' : 'false'" :class="[{ 'has-text-weight-bold' : ($i18n.locale === locale)}, 'has-text-' + businessInfoStore.linkTextColor]">
{{ locale.toUpperCase() }}
</a>
</span>
</span>
</div>
</template>
__test__/LocaleSwitcher.js
import LocaleSwitcher from '../LocaleSwitcher.vue'
import { createI18n } from 'vue-i18n'
import { mount } from '@cypress/vue'
import en from '../../locales/en.json'
import de from '../../locales/de.json'
import nl from '../../locales/nl.json'
describe('Test the LocaleSwitcher Languages selected',() => {
beforeEach(() => {
const availableMessages = { en, de, nl }
//load available languages
const i18n = createI18n({
legacy: false,
fallbackLocale: nl,
locale: nl,
globalInjection: true,
messages: availableMessages
})
//mount component with the new i18n object
cy.mount(LocaleSwitcher, { global: { plugins : [ i18n ]}})
})
it('Click first lang and check if ?LANG= is set correct', () => {
cy.get('[data-testid="DE"]').click()
cy.get('[data-current-locale="true"]').should('have.length', 1)
cy.get('[data-current-locale="true"]').should('contain', 'DE')
cy.location().should((loc) => {
expect(loc.search).to.contain('lang=de') <---- this is the url of the window not the component
})
})
})

Cypress tests typically run in a test runner environment, not directly in the context where the URL's query string can be manipulated as part of the component's initialization process.
And since you are working within a Vue Component test and Cypress's ecosystem, direct manipulation of
window.locationor mocking it in a way that affects the Vue component's behavior on initialization might not be straightforward or effective due to the test runner's isolated environment (see this question for illustration).You could try instead adjusting your component to make it more testable under these conditions: introduce a prop to the component that allows you to pass the initial URL or language parameter directly. That change makes the component's dependency on the global
location.hrefmore explicit and allows you to control it during testing.Modify your
LocaleSwitcher.jsto accept a prop for the initial URL or directly thelangparameter. If the prop is provided, use it; otherwise, default tolocation.href.Pass the simulated URL or language parameter when mounting the component in your test:
That should allow you to simulate different URL conditions by passing various
initialUrlprop values in your tests, facilitating the testing of component behavior based on URL parameters without relying on the actual browser environment or Cypress'scy.location().But... it requires modifying the component's implementation for testing purposes, which might not always be desirable.
Still, it does offer a controlled way to test behavior influenced by global objects like
location.hrefwithin the constraints of a component testing setup.Reading "Cypress Environment Variables", you could also try using
baseUrlwith dynamic paths. If your component's behavior is influenced by the path or query parameters in the URL, you can use Cypress'sbaseUrlconfiguration to set a base URL for your tests, and then navigate to specific paths or URLs in your tests:But as you noted,
cy.visit()is typically used in end-to-end (e2e) testing scenarios within Cypress, where the objective is to test the application in a manner that closely mimics real-world user interactions within a browser environment. Usingcy.visit()implies loading a whole page (either from a local development server or a live URL), rather than mounting a single Vue component in isolation.Since you want to test the
LocaleSwitchercomponent in isolation, the use ofcy.visit()is not applicable for component-level testing. Plus, you wish to limit modifications to the component.Mocking
window.locationdirectly in Cypress component tests can be complex due to the way the testing environment is sandboxed and managed.Another possibility involves exploring Cypress plugins or writing custom commands that might help simulate or mock the necessary environment or properties for your tests. That could include custom commands that simulate setting URL parameters or manipulating the browser's
localStorageandnavigatorproperties before mounting your component.That extension is typically done in the
commands.jsfile located in thecypress/supportdirectory.As noted by Dave.Tufford in the comments,
cy.on('window:before:load', (win) => {})only fires for e2e tests, i.e when usingcy.visit()., and for component tests, the component is mounted into the test runner window, not into an isolated iframe.A possible option to simulate the
window.locationor specifically the URL and its search parameters in a component test is to mock or overwrite the global JavaScript objects or functions your component uses to read these parameters: try and encapsulate the logic for getting URL parameters within a function or method that can be easily mocked or stubbed in our tests.First, make sure your component or its underlying logic uses a specific function to obtain URL parameters, making it easier to mock or override this behavior in tests.
Now you can create a Cypress custom command that allows you to stub this
getUrlParamfunction, making sure our component receives the mock URL parameters during tests.Assuming you have already made
getUrlParama method that can be imported and used in your component:Note: That assumes
getUrlParamis accessible globally or throughwindow, which might require adjusting how you define or import this function for it to be stubbable in the test context.Use this custom command in your component tests to simulate different URL parameters:
To export a function from a
<script setup>so it can be imported and used in other components or tests, you generally would not do this directly from the<script setup>tag because its primary use case is for component internals and setup.As a workaround, you can define functions or variables that should be globally accessible in a separate JavaScript or Vue file using standard export syntax and then import them into your
<script setup>component.For instance, create a separate
useUrlParams.jsfile:And import it in your
<script setup>Vue component:If you really need to attach a function to the
windowobject from within a<script setup>, which is generally not recommended due to potential namespace pollution and the risk of overwriting existing properties, you can do so directly:That makes
getUrlParamglobally accessible aswindow.getUrlParam.Be cautious with this approach, as attaching too many properties to the
windowobject can lead to harder-to-maintain code and potential conflicts.