Skip to content

Commit cca7a16

Browse files
authored
Merge branch '10.0-release' into tgriesser/feat/UNIFY-1267
2 parents 08c7ea0 + 1645431 commit cca7a16

File tree

26 files changed

+557
-3
lines changed

26 files changed

+557
-3
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,4 @@ system-tests/projects/nuxtjs-vue2-configured/**/*
9393
system-tests/projects/react-app-webpack-5-unconfigured/**/*
9494

9595
system-tests/project-fixtures/**
96+
system-tests/projects/**/*/expected-cypress*/**/*

packages/graphql/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"script"
1010
],
1111
"compilerOptions": {
12+
"lib": ["es2020"],
1213
"strict": true,
1314
"allowJs": false,
1415
"noImplicitAny": true,
@@ -19,4 +20,4 @@
1920
"importsNotUsedAsValues": "error",
2021
"types": []
2122
},
22-
}
23+
}

packages/launchpad/cypress.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { defineConfig } from 'cypress'
22
import getenv from 'getenv'
33
import { devServer } from '@cypress/vite-dev-server'
4+
import { snapshotCypressDirectory } from './cypress/tasks/snapshotsScaffold'
45

56
const CYPRESS_INTERNAL_CLOUD_ENV = getenv('CYPRESS_INTERNAL_CLOUD_ENV', process.env.CYPRESS_INTERNAL_ENV || 'development')
67

@@ -36,6 +37,10 @@ export default defineConfig({
3637
process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF = 'true'
3738
const { e2ePluginSetup } = require('@packages/frontend-shared/cypress/e2e/e2ePluginSetup')
3839

40+
on('task', {
41+
snapshotCypressDirectory,
42+
})
43+
3944
return await e2ePluginSetup(on, config)
4045
},
4146
},
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import type { FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
2+
import type { SnapshotScaffoldTestResult } from '@packages/launchpad/cypress/tasks/snapshotsScaffold'
3+
4+
// The tests in this file take an existing project without Cypress Configured
5+
// and add Cypress using the launchpad setup wizard.
6+
//
7+
// See `system-tests/projects/pristine` for an example.
8+
//
9+
// After adding a test for a project for the first time and running
10+
// this spec, it will see what files are scaffolded and save them
11+
// in a directory named `expected-cypress-{lang}-{testingType}`.
12+
// For example, when configuring the `pristine` project using
13+
// plain JS for E2E testing, it will make
14+
// a new directory in `pristine` named `expected-cypress-js-e2e`
15+
// containing the scaffolded files.
16+
//
17+
// Each subsequent run will compare the scaffolded files in the
18+
// `expected-cypress-js-e2e` directory to the newly created ones.
19+
//
20+
// If there is a discrepancy, the test will fail and show the diff in the command log.
21+
//
22+
// To update your expected files, just delete the `expected-cypress` and re-run the test,
23+
// or modify them by hand.
24+
function scaffoldAndOpenE2EProject (
25+
name: Parameters<typeof cy.scaffoldProject>[0],
26+
language: 'js' | 'ts',
27+
args?: Parameters<typeof cy.openProject>[1],
28+
) {
29+
cy.scaffoldProject(name)
30+
cy.openProject(name, args)
31+
32+
cy.visitLaunchpad()
33+
34+
cy.contains('Welcome to Cypress!').should('be.visible')
35+
cy.contains('[data-cy-testingtype="e2e"]', 'Not Configured')
36+
cy.contains('[data-cy-testingtype="component"]', 'Not Configured')
37+
cy.contains('E2E Testing').click()
38+
cy.contains(language === 'js' ? 'JavaScript' : 'TypeScript').click()
39+
cy.contains('Next').click()
40+
cy.contains('We added the following files to your project.')
41+
cy.contains('Continue').click()
42+
cy.contains('Choose a Browser')
43+
}
44+
45+
function scaffoldAndOpenCTProject (
46+
name: Parameters<typeof cy.scaffoldProject>[0],
47+
language: 'js' | 'ts',
48+
framework: typeof FRONTEND_FRAMEWORKS[number]['name'],
49+
bundler?: typeof FRONTEND_FRAMEWORKS[number]['supportedBundlers'][number]['name'],
50+
args?: Parameters<typeof cy.openProject>[1],
51+
) {
52+
cy.scaffoldProject(name)
53+
cy.openProject(name, args)
54+
55+
cy.visitLaunchpad()
56+
57+
cy.contains('Welcome to Cypress!').should('be.visible')
58+
cy.contains('[data-cy-testingtype="e2e"]', 'Not Configured')
59+
cy.contains('[data-cy-testingtype="component"]', 'Not Configured')
60+
cy.contains('Component Testing').click()
61+
cy.contains(language === 'js' ? 'JavaScript' : 'TypeScript').click()
62+
63+
cy.contains('Pick a framework').click()
64+
cy.contains(framework).click()
65+
if (bundler) {
66+
cy.contains(bundler).click()
67+
}
68+
69+
cy.contains('Next Step').click()
70+
cy.contains('Skip').click()
71+
cy.contains('We added the following files to your project.')
72+
cy.contains('Continue').click()
73+
cy.contains('Choose a Browser')
74+
}
75+
76+
function assertScaffoldedFilesAreCorrect (language: 'js' | 'ts', testingType: Cypress.TestingType, ctFramework?: string) {
77+
cy.withCtx((ctx) => ctx.currentProject).then((currentProject) => {
78+
cy.task<SnapshotScaffoldTestResult>('snapshotCypressDirectory', {
79+
currentProject,
80+
testingType,
81+
language,
82+
ctFramework,
83+
})
84+
.then((result) => {
85+
if (result.status === 'ok') {
86+
return result
87+
}
88+
89+
throw new Error(result.message)
90+
})
91+
.then((res) => {
92+
cy.log(`✅ ${res.message}`)
93+
})
94+
})
95+
}
96+
97+
describe('scaffolding new projects', () => {
98+
it('scaffolds E2E for a JS project', () => {
99+
scaffoldAndOpenE2EProject('pristine', 'js')
100+
assertScaffoldedFilesAreCorrect('js', 'e2e')
101+
})
102+
103+
it('scaffolds E2E for a TS project', () => {
104+
scaffoldAndOpenE2EProject('pristine', 'ts')
105+
assertScaffoldedFilesAreCorrect('ts', 'e2e')
106+
})
107+
108+
it('scaffolds CT for a JS project', () => {
109+
scaffoldAndOpenCTProject('pristine', 'js', 'Create React App (v5)')
110+
assertScaffoldedFilesAreCorrect('js', 'component', 'Create React App (v5)')
111+
})
112+
113+
it('scaffolds CT for a TS project', () => {
114+
scaffoldAndOpenCTProject('pristine', 'ts', 'Create React App (v5)')
115+
assertScaffoldedFilesAreCorrect('ts', 'component', 'Create React App (v5)')
116+
})
117+
})
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import path from 'path'
2+
import globby from 'globby'
3+
import fs from 'fs-extra'
4+
import disparity from 'disparity'
5+
import dedent from 'dedent'
6+
import { cyTmpDir } from '@tooling/system-tests/lib/fixtures'
7+
8+
const systemTestsDir = path.join(__dirname, '..', '..', '..', '..', 'system-tests', 'projects')
9+
10+
export interface SnapshotScaffoldTestResult {
11+
status: 'ok' | 'fail'
12+
message: string
13+
}
14+
15+
interface FileToDiff {
16+
actual: string
17+
expected: string
18+
}
19+
20+
async function snapshotScaffoldedFiles (expectedScaffoldDir: string, filesToDiff: FileToDiff[]): Promise<SnapshotScaffoldTestResult> {
21+
// see if existing snapshots exist compared to source
22+
// project in `system-tests/projects`
23+
// expected-cypress-{'js' | 'ts'}
24+
fs.mkdirSync(expectedScaffoldDir)
25+
26+
await Promise.all(filesToDiff.map(async (files) => {
27+
const { expected, actual } = files
28+
29+
await fs.ensureFile(expected)
30+
await fs.copy(actual, expected)
31+
await fs.copy(actual, expected)
32+
}))
33+
34+
return {
35+
status: 'ok',
36+
message: `Created expected files based on test`,
37+
}
38+
}
39+
40+
function compareScaffoldedFiles (filesToDiff: FileToDiff[]): SnapshotScaffoldTestResult {
41+
for (const { actual, expected } of filesToDiff) {
42+
try {
43+
const read = (f: string) => fs.readFileSync(f, 'utf-8')
44+
const actualContent = read(actual).trim()
45+
const expectedContent = read(expected).trim()
46+
const diff = disparity.unifiedNoColor(actualContent, expectedContent, {})
47+
48+
if (diff !== '') {
49+
return {
50+
status: 'fail',
51+
message: `Expected contents of ${actual} and ${expected} to match. Diff: ${diff}`,
52+
}
53+
}
54+
} catch (err) {
55+
const e = err as NodeJS.ErrnoException
56+
57+
if (e.code === 'ENOENT') {
58+
return {
59+
status: 'fail',
60+
message: `Expected ${e.path} to exist, but it was not found`,
61+
}
62+
}
63+
64+
return {
65+
status: 'fail',
66+
message: `Unexpected error: ${e.message}`,
67+
}
68+
}
69+
}
70+
71+
return {
72+
status: 'ok',
73+
message: 'Scaffolded files matched expected files',
74+
}
75+
}
76+
77+
interface SnapshotCypressDirectoryOptions {
78+
currentProject: string
79+
language: 'js' | 'ts'
80+
testingType: Cypress.TestingType
81+
ctFramework?: string
82+
}
83+
84+
function removeHyphensAndBrackets (str: string) {
85+
return str.toLowerCase().replaceAll(' ', '-').replaceAll('(', '').replaceAll(')', '')
86+
}
87+
88+
export async function snapshotCypressDirectory ({ currentProject, language, testingType, ctFramework }: SnapshotCypressDirectoryOptions): Promise<SnapshotScaffoldTestResult> {
89+
if (!currentProject.startsWith(cyTmpDir)) {
90+
throw Error(dedent`
91+
snapshotCypressDirectory is designed to be used with system-tests infrastructure.
92+
It should not be used with projects that are not created in a temporary directory
93+
by the system-tests infrastructure.
94+
95+
Expected currentProject to be in /tmp. currentProject found at path ${currentProject}`)
96+
}
97+
98+
const projectDir = currentProject.replace(cyTmpDir, systemTestsDir)
99+
const projectName = projectDir.replace(systemTestsDir, '').slice(1)
100+
101+
let expectedScaffoldDir = path.join(projectDir, `expected-cypress-${language}-${testingType}`)
102+
103+
if (ctFramework) {
104+
expectedScaffoldDir += `-${removeHyphensAndBrackets(ctFramework)}`
105+
}
106+
107+
const joinPosix = (...s: string[]) => path.join(...s).split(path.sep).join(path.posix.sep)
108+
109+
const files = (
110+
await Promise.all([
111+
globby(joinPosix(currentProject, 'cypress'), { onlyFiles: true }),
112+
globby(joinPosix(currentProject, 'cypress.config.*'), { onlyFiles: true }),
113+
])
114+
).reduce((acc, curr) => {
115+
return [acc, curr].flat(2)
116+
}, [])
117+
118+
const actualRelativeFiles = files.map((file) => {
119+
return file.replace(cyTmpDir, '').slice(projectName.length + 2)
120+
})
121+
122+
const filesToDiff = actualRelativeFiles.map<FileToDiff>((file) => {
123+
return {
124+
actual: path.join(currentProject, file),
125+
expected: path.join(expectedScaffoldDir, file),
126+
}
127+
})
128+
129+
if (fs.existsSync(expectedScaffoldDir)) {
130+
// compare!
131+
return compareScaffoldedFiles(filesToDiff)
132+
}
133+
134+
return snapshotScaffoldedFiles(expectedScaffoldDir, filesToDiff)
135+
}

packages/launchpad/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
"@percy/cypress": "^3.1.0",
3131
"@purge-icons/generated": "0.8.1",
3232
"@testing-library/cypress": "8.0.0",
33+
"@tooling/system-tests": "0.0.0-development",
3334
"@toycode/markdown-it-class": "1.2.3",
35+
"@types/dedent": "^0.7.0",
36+
"@types/fs-extra": "^8.0.1",
3437
"@urql/core": "2.3.1",
3538
"@urql/devtools": "2.0.3",
3639
"@urql/vue": "0.4.3",
@@ -46,6 +49,10 @@
4649
"cross-env": "6.0.3",
4750
"cypress-plugin-tab": "1.0.5",
4851
"cypress-real-events": "1.6.0",
52+
"dedent": "^0.7.0",
53+
"disparity": "^3.0.0",
54+
"fs-extra": "8.1.0",
55+
"globby": "^11.0.1",
4956
"graphql": "^15.5.1",
5057
"graphql-tag": "^2.12.5",
5158
"gravatar": "1.8.0",

packages/server/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"./../ts/index.d.ts"
99
],
1010
"compilerOptions": {
11+
"lib": ["es2020"],
1112
"types": [
1213
"mocha",
1314
"node"
@@ -16,4 +17,4 @@
1617
"noUnusedLocals": false,
1718
"importHelpers": true
1819
}
19-
}
20+
}

system-tests/lib/fixtures.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ const projectFixtureDirs = fs.readdirSync(projectFixtures, { withFileTypes: true
1818
const safeRemove = (path) => {
1919
try {
2020
fs.removeSync(path)
21-
} catch (err) {
21+
} catch (_err) {
22+
const err = _err as NodeJS.ErrnoException
23+
2224
// Windows does not like the en masse deleting of files, since the AV will hold
2325
// a lock on files when they are written. This skips deleting if the lock is
2426
// encountered.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { devServer } = require('@cypress/react/plugins/react-scripts')
2+
3+
module.exports = {
4+
component: {
5+
devServer,
6+
},
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "Using fixtures to represent data",
3+
"email": "hello@cypress.io",
4+
"body": "Fixtures are a great way to mock data for responses to routes"
5+
}

0 commit comments

Comments
 (0)