-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(vite-dev-server): Retry dynamic importing of support file and spec file when they fail during CT run with Vite #26202
Changes from all commits
3abe635
a478d7d
6cecebc
240a4c0
164ef2e
ed984ef
243502a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,6 +1,42 @@ | ||||||
// This file is merged in a <script type=module> into index.html | ||||||
// it will be used to load and kick start the selected spec | ||||||
|
||||||
// Fetch a dynamic import and re-try 3 times with a 2-second back-off. | ||||||
// We want to re-try these if they fail because sometimes (seemingly due to network issues) | ||||||
// the modules aren't available when we first request them. | ||||||
// See https://github.com/cypress-io/cypress/issues/25913 | ||||||
async function importWithRetry (importFn) { | ||||||
try { | ||||||
return await importFn() | ||||||
} catch (error) { | ||||||
for (let i = 0; i < 3; i++) { | ||||||
await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** i)) | ||||||
|
||||||
let url | ||||||
|
||||||
try { | ||||||
// Get request URL from error message from original import | ||||||
url = new URL( | ||||||
error.message | ||||||
.replace('Failed to fetch dynamically imported module: ', '') | ||||||
.trim(), | ||||||
) | ||||||
|
||||||
console.error(`retrying import of ${url?.href}`) | ||||||
|
||||||
// add a timestamp to the end of the URL to force re-load the module instead of using the cache | ||||||
url.searchParams.set('t', `${+new Date()}`) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
return await import(url.href) | ||||||
} catch (e) { | ||||||
console.error(`retrying import of ${url?.href}`) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick - will print a message that we're going to retry on the last attempt There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, maybe more of a warning than an error if we're potentially going to recover from it with a retry. A total failure constitutes an error, at least in my mind |
||||||
} | ||||||
} | ||||||
|
||||||
throw error | ||||||
} | ||||||
} | ||||||
|
||||||
const CypressInstance = window.Cypress = parent.Cypress | ||||||
|
||||||
const importsToLoad = [] | ||||||
|
@@ -25,7 +61,7 @@ if (supportFile) { | |||||
// We need a slash before /cypress/supportFile.js, this happens by default | ||||||
// with the current string replacement logic. | ||||||
importsToLoad.push({ | ||||||
load: () => import(`${devServerPublicPathRoute}${supportRelativeToProjectRoot}`), | ||||||
load: () => importWithRetry(() => import(`${devServerPublicPathRoute}${supportRelativeToProjectRoot}`)), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we refactor |
||||||
absolute: supportFile, | ||||||
relative: supportRelativeToProjectRoot, | ||||||
relativeUrl: `${devServerPublicPathRoute}${supportRelativeToProjectRoot}`, | ||||||
|
@@ -41,7 +77,7 @@ const testFileAbsolutePathRoute = `${devServerPublicPathRoute}/@fs/${normalizedA | |||||
/* Spec file import logic */ | ||||||
// We need a slash before /src/my-spec.js, this does not happen by default. | ||||||
importsToLoad.push({ | ||||||
load: () => import(testFileAbsolutePathRoute), | ||||||
load: () => importWithRetry(() => import(testFileAbsolutePathRoute)), | ||||||
absolute: CypressInstance.spec.absolute, | ||||||
relative: CypressInstance.spec.relative, | ||||||
relativeUrl: testFileAbsolutePathRoute, | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -115,6 +115,21 @@ describe('Config options', () => { | |
expect(verifyFile).to.eq('OK') | ||
}) | ||
}) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great to see a test. Didn't we decide this would be a run-mode only thing, though (this test is for open mode - you'd need to write it in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right - while I was struggling to write a system test I started to question why this should be run only. Maybe the reason that it should be is that we don't want to modify the dynamic import behavior in open mode? We could make it run only and I'll have to come up with a testing strategy that doesn't use |
||
it('retries importing support file if the import errors', () => { | ||
cy.intercept('**/cypress/support/component.js', { | ||
statusCode: 404, | ||
}) | ||
|
||
cy.scaffoldProject('vite2.9.1-react') | ||
cy.openProject('vite2.9.1-react', ['--config-file', 'cypress-vite.config.ts']) | ||
cy.startAppServer('component') | ||
|
||
cy.visitApp() | ||
cy.contains('App.cy.jsx').click() | ||
cy.waitForSpecToFinish() | ||
cy.get('.passed > .num').should('contain', 2) | ||
}) | ||
}) | ||
|
||
describe('sourcemaps', () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just out of curiosity, what sort of details do we get from this error message? Can we tell the difference between a module not being ready and the dev server being totally hosed (misconfigured, etc) so we don't unnecessarily hang the test on retries if it can't possibly succeed?