nice-summer-59524
05/23/2023, 3:19 AMflags property to the test options.
I've attempted to make that request a couple ways, but each attempt has either failed, or been flaky due to race conditions.
1. Modify cy.visit using Cypress.Commands.overwrite, so that flag overrides are applied before each visit.
js
Cypress.Commands.overwrite('visit', (original, url, options) => {
cy.request({
method: 'POST',
url: '/dev/flags/override',
body: Cypress.config('flags') || {},
}).then(() => {
original(url, options);
});
});
This is the most straight forward solution, and seems to correctly apply flags for the first test of each spec. However when this override is applied, an error begins occurring in specs that call visit in beforeEach.
CypressError: Timed out retrying after 4050ms: `cy.click()` failed because the page updated while this command was executing.
OR
Timed out retrying after 4050ms: cy.first() failed because the page updated as a result of this command, but you tried to continue the command chain. The subject is no longer attached to the DOM, and Cypress cannot requery the page after commands such as cy.first().
The first test in a spec will pass normally, but each test after that will fail.
I suspected it might be because the newly overwritten visit command resolves when the override request completes, rather than when the original call completes. So I tried to address that manually:
js
Cypress.Commands.overwrite(
'visit',
(original, url, options) =>
new Promise((resolve) => {
cy.request({
method: 'POST',
url: '/dev/flags/override',
body: Cypress.config('flags') || {},
}).then(() => {
original(url, options).then(resolve);
});
}),
);
This didn't work because Cypress commands doesn't use promises.
2. Run the flag override request in global before and beforeEach hooks
This worked sometimes, but was flaky. My understanding is that due to the way Cypress schedules tasks, it's not possible to guarantee that tasks scheduled in the global before and beforeEach hooks would resolve before tasks in the spec's before and beforeEach hooks would run. This means if there's any latency in the flag override request, the override would not be ready by the time cy.visit is called.
js
const requestFlags = () =>
cy.request({
method: 'POST',
url: '/dev/flags/override',
body: Cypress.config('flags') || {},
}),
});
before(() => {
requestFlags();
});
beforeEach(() => {
requestFlags();
});
requestFlags would need to be run in both before and beforeEach, because some of our specs do navigation in a before hook. In which case, the navigation would occur before the requestFlags call in the beforeEach hook. We're working to move away from calling visit in before hooks, because we want tests to be better decoupled from each other. But that's a work in progress.
Unfortunately, I cannot provide a reproduction repo at this time, as this is a proprietary project. If needed, I can try throwing together a minimum implementation of a server/client using this flag override approach for the purpose of debugging this and coming to a better solution. Though I'm hoping someone with more Cypress experience might be able to point out what I'm doing wrong / if there's a better approach. Any assistance would be greatly appreciated, thank you 🙂enough-truck-68085
05/23/2023, 1:58 PMlogin command.
/** Login with the API */
Cypress.Commands.add('login', ({ user } = {}) => {
cy.loginWithAPI({ user }).then(() => {
cy.getAllFeatureFlags()
})
})
I then just call this cy.login command in a beforeEach hook.
By wrapping this request in a custom command you are able to enforce synchronously using .then()
https://docs.cypress.io/guides/core-concepts/variables-and-aliases#Closures
For you, in you beforeEach, you can always do something like below
cy.requestFlags().then(() => {
cy.visit('/')
})
Alternatively, just calling cy.requestFlags() in a before hook should also be enough for the command to finish before moving forward.
Since my feature flagging request is bundled with my login request, which uses cy.session, I only call it once per test suite, even though it's used in the beforeEach. I'm able to persist the fetched feature flag values by using a Cypress.env variable.
You can see I basically save off the fetched flags after making the initial response.
I can then access that same Cypress.env variable and use it however I want in my tests.
Cypress.Commands.add('getAllFeatureFlags', () => {
Cypress.log({ name: 'getAllFeatureFlags' })
return cy
.request({
method: 'GET',
url: '/feature-flags',
headers: {
Accept: 'application/json',
},
})
.then((rsp) => {
Cypress.env('featureFlags', rsp)
})
})
I'm not sure if I addressed everything you needed as there was a lot your question so please clarify if I missed anything of if the explanation isn't clear.gray-kilobyte-89541
05/23/2023, 2:20 PMcy.then, like
js
cy.requestFlags().then(() => {
cy.visit('/')
})
is the same as
js
cy.requestFlags()
cy.visit('/')
For feature flags, check out my blogs at https://cypress.tips/search