From 7489561fbfb676a8948eff89730b99bf4d4fc63e Mon Sep 17 00:00:00 2001 From: Tomas Bjerre Date: Tue, 16 Aug 2022 15:49:56 +0200 Subject: [PATCH 1/3] feat: including version in CYPRESS_DOWNLOAD_PATH_TEMPLATE (refs #5141 #16976 #22864) (#23194) Co-authored-by: Blue F Co-authored-by: Matt Henkes --- cli/__snapshots__/download_spec.js | 4 ++++ cli/lib/tasks/download.js | 7 +++++-- cli/test/lib/tasks/download_spec.js | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/cli/__snapshots__/download_spec.js b/cli/__snapshots__/download_spec.js index 1a9f8c60ccad..c913289d5846 100644 --- a/cli/__snapshots__/download_spec.js +++ b/cli/__snapshots__/download_spec.js @@ -45,6 +45,10 @@ exports['desktop url from template'] = ` https://download.cypress.io/desktop/0.20.2/OS-ARCH/cypress.zip ` +exports['desktop url from template with version'] = ` +https://mycompany/0.20.2/OS-ARCH/cypress.zip +` + exports['desktop url from template with escaped dollar sign'] = ` https://download.cypress.io/desktop/0.20.2/OS-ARCH/cypress.zip ` diff --git a/cli/lib/tasks/download.js b/cli/lib/tasks/download.js index 1aab08c2a85a..6b8cd38b69b2 100644 --- a/cli/lib/tasks/download.js +++ b/cli/lib/tasks/download.js @@ -60,7 +60,7 @@ const getCA = () => { }) } -const prepend = (arch, urlPath) => { +const prepend = (arch, urlPath, version) => { const endpoint = url.resolve(getBaseUrl(), urlPath) const platform = os.platform() const pathTemplate = util.getEnv('CYPRESS_DOWNLOAD_PATH_TEMPLATE', true) @@ -71,6 +71,7 @@ const prepend = (arch, urlPath) => { .replace(/\\?\$\{endpoint\}/, endpoint) .replace(/\\?\$\{platform\}/, platform) .replace(/\\?\$\{arch\}/, arch) + .replace(/\\?\$\{version\}/, version) ) : `${endpoint}?platform=${platform}&arch=${arch}` } @@ -82,7 +83,9 @@ const getUrl = (arch, version) => { return version } - return version ? prepend(arch, `desktop/${version}`) : prepend(arch, 'desktop') + const urlPath = version ? `desktop/${version}` : 'desktop' + + return prepend(arch, urlPath, version) } const statusMessage = (err) => { diff --git a/cli/test/lib/tasks/download_spec.js b/cli/test/lib/tasks/download_spec.js index ee88fc234de5..13c1176795b4 100644 --- a/cli/test/lib/tasks/download_spec.js +++ b/cli/test/lib/tasks/download_spec.js @@ -74,6 +74,13 @@ describe('lib/tasks/download', function () { snapshot('desktop url from template', normalize(url)) }) + it('returns custom url from template with version', () => { + process.env.CYPRESS_DOWNLOAD_PATH_TEMPLATE = 'https://mycompany/${version}/${platform}-${arch}/cypress.zip' + const url = download.getUrl('ARCH', '0.20.2') + + snapshot('desktop url from template with version', normalize(url)) + }) + it('returns custom url from template with escaped dollar sign', () => { process.env.CYPRESS_DOWNLOAD_PATH_TEMPLATE = '\\${endpoint}/\\${platform}-\\${arch}/cypress.zip' const url = download.getUrl('ARCH', '0.20.2') From ae5cbb592d0579da51e733853cf5c7da927db89a Mon Sep 17 00:00:00 2001 From: Emily Rohrbough Date: Tue, 16 Aug 2022 09:27:28 -0500 Subject: [PATCH 2/3] fix: persist registered spec sessions (#23347) --- .../app/cypress/e2e/runner/sessions.ui.cy.ts | 81 +++++++++++-------- .../e2e/commands/sessions/manager.cy.ts | 4 +- .../e2e/commands/sessions/sessions.cy.js | 23 +++--- .../driver/src/cy/commands/sessions/index.ts | 12 ++- .../src/cy/commands/sessions/manager.ts | 2 +- system-tests/__snapshots__/session_spec.ts.js | 13 ++- .../projects/e2e/cypress/e2e/session.cy.js | 22 +---- .../e2e/session/restores_saved_session.cy.js | 23 ++++++ 8 files changed, 100 insertions(+), 80 deletions(-) diff --git a/packages/app/cypress/e2e/runner/sessions.ui.cy.ts b/packages/app/cypress/e2e/runner/sessions.ui.cy.ts index e3909aee6d31..329d5c48e4dc 100644 --- a/packages/app/cypress/e2e/runner/sessions.ui.cy.ts +++ b/packages/app/cypress/e2e/runner/sessions.ui.cy.ts @@ -131,51 +131,68 @@ describe('runner/cypress sessions.ui.spec', { cy.percySnapshot() }) - it('restores saved session', () => { - loadSpec({ - projectName: 'session-and-origin-e2e-specs', - filePath: 'session/restores_saved_session.cy.js', - passCount: 2, + describe('restores saved session', () => { + beforeEach(() => { + loadSpec({ + projectName: 'session-and-origin-e2e-specs', + filePath: 'session/restores_saved_session.cy.js', + passCount: 5, + failCount: 1, + }) }) - cy.get('.test').each(($el) => cy.wrap($el).click()) + it('restores session as expected', () => { + cy.get('.test').each(($el) => cy.wrap($el).click()) - cy.log('validate new session was created in first test') - cy.get('.test').eq(0).within(() => { - validateSessionsInstrumentPanel(['user1']) - cy.get('.command-name-session').contains('created') - }) + cy.log('validate new session was created in first test') + cy.get('.test').eq(0).within(() => { + validateSessionsInstrumentPanel(['user1']) + cy.get('.command-name-session').contains('created') + }) - cy.log('validate saved session was used in second test') - cy.get('.test').eq(1).within(() => { - validateSessionsInstrumentPanel(['user1']) + cy.log('validate saved session was used in second test') + cy.get('.test').eq(1).within(() => { + validateSessionsInstrumentPanel(['user1']) - cy.get('.command-name-session') - .within(() => { - cy.get('.command-expander').first().click() - cy.contains('user1') - cy.contains('restored') + cy.get('.command-name-session') + .within(() => { + cy.get('.command-expander').first().click() + cy.contains('user1') + cy.contains('restored') - cy.get('.command-name-Clear-page').should('have.length', 2) + cy.get('.command-name-Clear-page').should('have.length', 2) - cy.contains('Restore saved session') + cy.contains('Restore saved session') - cy.contains('Validate session') - .closest('.command').as('validateSession') + cy.contains('Validate session') + .closest('.command').as('validateSession') - cy.get('@validateSession') - .find('.command-expander') - .should('not.have.class', 'command-expander-is-open') - .click() + cy.get('@validateSession') + .find('.command-expander') + .should('not.have.class', 'command-expander-is-open') + .click() - cy.get('@validateSession') - .find('.command-alias') - .contains('runValidation') + cy.get('@validateSession') + .find('.command-alias') + .contains('runValidation') + }) + + cy.get('.command-name-session').get('.command-expander').first().click() + + cy.get('.command').should('have.length', 2) }) + }) - cy.get('.command-name-session').get('.command-expander').first().click() + // https://github.com/cypress-io/cypress/issues/22381 + it('ensures sessionid integrity is maintained across tests', () => { + cy.contains('test sessionid integrity is maintained').closest('.runnable').should('have.class', 'runnable-failed') + cy.get('.test').should('have.length', 6) - cy.get('.command').should('have.length', 2) + cy.get('.test').eq(2).should('have.class', 'runnable-passed') + cy.get('.test').eq(3).should('have.class', 'runnable-passed') + cy.get('.test').eq(4).should('have.class', 'runnable-passed') + cy.get('.test').eq(5).should('have.class', 'runnable-failed') + cy.contains('If you want to specify different options, please use a unique name').should('exist') }) }) diff --git a/packages/driver/cypress/e2e/commands/sessions/manager.cy.ts b/packages/driver/cypress/e2e/commands/sessions/manager.cy.ts index 433e6e7b0b5a..33d237939431 100644 --- a/packages/driver/cypress/e2e/commands/sessions/manager.cy.ts +++ b/packages/driver/cypress/e2e/commands/sessions/manager.cy.ts @@ -16,8 +16,8 @@ describe('src/cy/commands/sessions/manager.ts', () => { expect(sessionsManager).to.haveOwnProperty('cy') expect(sessionsManager).to.haveOwnProperty('Cypress') - expect(sessionsManager).to.haveOwnProperty('currentTestRegisteredSessions') - expect(sessionsManager.currentTestRegisteredSessions).to.be.instanceOf(Map) + expect(sessionsManager).to.haveOwnProperty('registeredSessions') + expect(sessionsManager.registeredSessions).to.be.instanceOf(Map) }) describe('.setActiveSession()', () => { diff --git a/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js b/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js index 639d439e2cc0..ca5efc02f683 100644 --- a/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js +++ b/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js @@ -24,6 +24,7 @@ describe('cy.session', { retries: 0 }, () => { it('accepts array as id', () => { cy.session('session-id', () => {}) + cy.session('session-id') }) it('accepts object as id', () => { @@ -35,7 +36,7 @@ describe('cy.session', { retries: 0 }, () => { const setup = cy.stub().as('setupSession') const validate = cy.stub().as('validateSession') - cy.session('session-id', setup, { validate }) + cy.session('session-id-3rd-arg', setup, { validate }) cy.then(() => { expect(setup).to.be.calledOnce expect(validate).to.be.calledOnce @@ -236,7 +237,7 @@ describe('cy.session', { retries: 0 }, () => { setupTestContext() cy.log('Creating new session with validation to test against') - cy.session('session-1', setup, { validate }) + cy.session(`session-${Cypress.state('test').id}`, setup, { validate }) cy.url().should('eq', 'about:blank') }) @@ -366,7 +367,7 @@ describe('cy.session', { retries: 0 }, () => { validate.callsFake(() => false) - cy.session('session-1', setup, { validate }) + cy.session(`session-${Cypress.state('test').id}`, setup, { validate }) }) }) @@ -374,14 +375,14 @@ describe('cy.session', { retries: 0 }, () => { before(() => { setupTestContext() cy.log('Creating new session for test') - cy.session('session-1', setup) + cy.session(`session-${Cypress.state('test').id}`, setup) .then(() => { // reset and only test restored session resetMocks() }) cy.log('restore session to test against') - cy.session('session-1', setup) + cy.session(`session-${Cypress.state('test').id}`, setup) cy.url().should('eq', 'about:blank') }) @@ -433,14 +434,14 @@ describe('cy.session', { retries: 0 }, () => { before(() => { setupTestContext() cy.log('Creating new session for test') - cy.session('session-1', setup, { validate }) + cy.session(`session-${Cypress.state('test').id}`, setup, { validate }) .then(() => { // reset and only test restored session resetMocks() }) cy.log('restore session to test against') - cy.session('session-1', setup, { validate }) + cy.session(`session-${Cypress.state('test').id}`, setup, { validate }) cy.url().should('eq', 'about:blank') }) @@ -504,7 +505,7 @@ describe('cy.session', { retries: 0 }, () => { before(() => { setupTestContext() cy.log('Creating new session for test') - cy.session('session-1', setup, { validate }) + cy.session(`session-${Cypress.state('test').id}`, setup, { validate }) .then(() => { // reset and only test restored session resetMocks() @@ -516,7 +517,7 @@ describe('cy.session', { retries: 0 }, () => { }) cy.log('restore session to test against') - cy.session('session-1', setup, { validate }) + cy.session(`session-${Cypress.state('test').id}`, setup, { validate }) cy.url().should('eq', 'about:blank') }) @@ -627,7 +628,7 @@ describe('cy.session', { retries: 0 }, () => { it('fails to recreate session and logs correctly', function (done) { setupTestContext() cy.log('Creating new session for test') - cy.session('session-1', setup, { validate }) + cy.session(`session-${Cypress.state('test').id}`, setup, { validate }) .then(() => { // reset and only test restored session resetMocks() @@ -733,7 +734,7 @@ describe('cy.session', { retries: 0 }, () => { }) cy.log('restore session to test against') - cy.session('session-1', setup, { validate }) + cy.session(`session-${Cypress.state('test').id}`, setup, { validate }) }) }) }) diff --git a/packages/driver/src/cy/commands/sessions/index.ts b/packages/driver/src/cy/commands/sessions/index.ts index 31ee24a0f1af..196113b136ca 100644 --- a/packages/driver/src/cy/commands/sessions/index.ts +++ b/packages/driver/src/cy/commands/sessions/index.ts @@ -40,8 +40,6 @@ export default function (Commands, Cypress, cy) { Cypress.on('run:start', () => { Cypress.on('test:before:run:async', () => { if (Cypress.config('experimentalSessionAndOrigin')) { - sessionsManager.currentTestRegisteredSessions.clear() - const clearPage = Cypress.config('testIsolation') === 'strict' ? navigateAboutBlank(false) : new Cypress.Promise.resolve() return clearPage @@ -92,18 +90,18 @@ export default function (Commands, Cypress, cy) { } let existingSession: SessionData = sessionsManager.getActiveSession(id) - const isRegisteredSessionForTest = sessionsManager.currentTestRegisteredSessions.has(id) + const isRegisteredSessionForSpec = sessionsManager.registeredSessions.has(id) if (!setup) { - if (!existingSession || !isRegisteredSessionForTest) { + if (!existingSession || !isRegisteredSessionForSpec) { $errUtils.throwErrByPath('sessions.session.not_found', { args: { id } }) } } else { const isUniqSessionDefinition = !existingSession || existingSession.setup.toString().trim() !== setup.toString().trim() if (isUniqSessionDefinition) { - if (isRegisteredSessionForTest) { - $errUtils.throwErrByPath('sessions.session.duplicateId', { args: { id: existingSession.id } }) + if (isRegisteredSessionForSpec) { + $errUtils.throwErrByPath('sessions.session.duplicateId', { args: { id } }) } existingSession = sessions.defineSession({ @@ -112,7 +110,7 @@ export default function (Commands, Cypress, cy) { validate: options.validate, }) - sessionsManager.currentTestRegisteredSessions.set(id, true) + sessionsManager.registeredSessions.set(id, true) } } diff --git a/packages/driver/src/cy/commands/sessions/manager.ts b/packages/driver/src/cy/commands/sessions/manager.ts index 5a59d9129870..cb71299c61e7 100644 --- a/packages/driver/src/cy/commands/sessions/manager.ts +++ b/packages/driver/src/cy/commands/sessions/manager.ts @@ -25,7 +25,7 @@ const getLogProperties = (displayName) => { export default class SessionsManager { Cypress cy - currentTestRegisteredSessions = new Map() + registeredSessions = new Map() constructor (Cypress, cy) { this.Cypress = Cypress diff --git a/system-tests/__snapshots__/session_spec.ts.js b/system-tests/__snapshots__/session_spec.ts.js index 00830849cea7..3a46192cc7d9 100644 --- a/system-tests/__snapshots__/session_spec.ts.js +++ b/system-tests/__snapshots__/session_spec.ts.js @@ -86,9 +86,6 @@ exports['e2e sessions / session tests'] = ` can wait for login redirect automatically ✓ t1 - can wait for a js redirect with an assertion - ✓ t1 - same session name, different options, multiple tests ✓ t1 ✓ t2 @@ -107,15 +104,15 @@ exports['e2e sessions / session tests'] = ` ✓ clears only secure context data - 2/2 - 43 passing + 42 passing 1 pending (Results) ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Tests: 44 │ - │ Passing: 43 │ + │ Tests: 43 │ + │ Passing: 42 │ │ Failing: 0 │ │ Pending: 1 │ │ Skipped: 0 │ @@ -133,9 +130,9 @@ exports['e2e sessions / session tests'] = ` Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ session.cy.js XX:XX 44 43 - 1 - │ + │ ✔ session.cy.js XX:XX 43 42 - 1 - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✔ All specs passed! XX:XX 44 43 - 1 - + ✔ All specs passed! XX:XX 43 42 - 1 - ` diff --git a/system-tests/projects/e2e/cypress/e2e/session.cy.js b/system-tests/projects/e2e/cypress/e2e/session.cy.js index 3d8255b60f25..257e2250e5cf 100644 --- a/system-tests/projects/e2e/cypress/e2e/session.cy.js +++ b/system-tests/projects/e2e/cypress/e2e/session.cy.js @@ -405,7 +405,7 @@ describe('options.validate reruns steps when rejecting', () => { }) describe('options.validate reruns steps when throwing', () => { - SuiteWithValidateFn('validate_reject', (callCount) => { + SuiteWithValidateFn('validate_throw', (callCount) => { if (callCount === 2) { throw new Error('validate error') } @@ -460,22 +460,6 @@ describe('can wait for login redirect automatically', () => { cy.session('redirect-login', () => { cy.visit('https://localhost:4466/form') cy.get('[name="delay"]').type('100{enter}') - // not needed since cypress will pause command queue during the redirect - // cy.url().should('include', '/home') - }) - - expectCurrentSessionData({ - cookies: ['/form', '/home'], - }) - }) -}) - -describe('can wait for a js redirect with an assertion', () => { - it('t1', () => { - cy.session('redirect-login', () => { - cy.visit('https://localhost:4466/form') - cy.get('[name="delay"]').type('100{enter}') - // cy.url().should('include', '/home') }) expectCurrentSessionData({ @@ -486,7 +470,7 @@ describe('can wait for a js redirect with an assertion', () => { describe('same session name, different options, multiple tests', () => { it('t1', () => { - cy.session('bob', () => { + cy.session('bob_1', () => { localStorage.bob = '1' }) .then(() => { @@ -495,7 +479,7 @@ describe('same session name, different options, multiple tests', () => { }) it('t2', () => { - cy.session('bob', () => { + cy.session('bob_2', () => { localStorage.bob = '2' }) .then(() => { diff --git a/system-tests/projects/session-and-origin-e2e-specs/cypress/e2e/session/restores_saved_session.cy.js b/system-tests/projects/session-and-origin-e2e-specs/cypress/e2e/session/restores_saved_session.cy.js index 4640c687c030..382ee251e972 100644 --- a/system-tests/projects/session-and-origin-e2e-specs/cypress/e2e/session/restores_saved_session.cy.js +++ b/system-tests/projects/session-and-origin-e2e-specs/cypress/e2e/session/restores_saved_session.cy.js @@ -21,3 +21,26 @@ it('t2', () => { cy.log('after') }) + +// https://github.com/cypress-io/cypress/issues/22381 +describe('test sessionid integrity is maintained', () => { + it('use same session 2x and 2nd does not provide setup', () => { + cy.session('session-2', setupFn) + cy.session('session-2') + }) + + it('restore prev session 2x and 2nd does not provide setup', () => { + cy.session('session-2', setupFn) + cy.session('session-2') + }) + + it('restore prev session without setup', () => { + cy.session('session-2') + }) + + it('fails when trying to use existing sessionid with diff args', () => { + cy.session('session-2', () => { + // something else + }) + }) +}) From 937d4e7f738642b4d08496347c4b868864e2c758 Mon Sep 17 00:00:00 2001 From: Tyler Biethman Date: Tue, 16 Aug 2022 10:09:59 -0500 Subject: [PATCH 3/3] fix: ensure absolute directories in glob patterns are leading prior to removal (#23368) * fix: ensure absolute directories in glob patterns are leading prior to removal * Removing errant slash --- .../data-context/src/sources/FileDataSource.ts | 6 +++++- .../test/unit/sources/FileDataSource.spec.ts | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/data-context/src/sources/FileDataSource.ts b/packages/data-context/src/sources/FileDataSource.ts index 410776163a43..73351368be32 100644 --- a/packages/data-context/src/sources/FileDataSource.ts +++ b/packages/data-context/src/sources/FileDataSource.ts @@ -36,7 +36,11 @@ export class FileDataSource { // The working directory path may include characters that conflict with glob // syntax (brackets, parentheses, etc.) and cause our searches to inadvertently fail. // We scope our search to the working directory using the `cwd` globby option. - return globPattern.replace(cwd, '.') + if (globPattern.startsWith(cwd)) { + return globPattern.replace(cwd, '.') + } + + return globPattern }) const ignoreGlob = (globOptions?.ignore ?? []).concat('**/node_modules/**') diff --git a/packages/data-context/test/unit/sources/FileDataSource.spec.ts b/packages/data-context/test/unit/sources/FileDataSource.spec.ts index c7f02869435f..9d8f2a3c55a6 100644 --- a/packages/data-context/test/unit/sources/FileDataSource.spec.ts +++ b/packages/data-context/test/unit/sources/FileDataSource.spec.ts @@ -58,6 +58,24 @@ describe('FileDataSource', () => { expect(files).to.have.length(2) }) + it('does not replace working directory in glob pattern if it is not leading', async () => { + // Create a redundant structure within the project dir matching its absolute path + // and write a new script in that location + const nestedScriptPath = path.join(projectPath, 'cypress', projectPath) + + await fs.mkdirs(nestedScriptPath) + await fs.writeFile(path.join(nestedScriptPath, 'nested-script.js'), '') + + // Verify that the glob pattern is not impacted if if contains directories equivalent + // to the working directory + let files = await fileDataSource.getFilesByGlob( + projectPath, + `./cypress${projectPath}/nested-script.js`, + ) + + expect(files).to.have.length(1) + }) + it('finds files matching multiple patterns', async () => { const files = await fileDataSource.getFilesByGlob( projectPath,