diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ac4a75e150b..c23196e5ae80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -185,7 +185,7 @@ jobs: name: Build needs: [job_get_metadata, job_install_deps] runs-on: ubuntu-20.04 - timeout-minutes: 20 + timeout-minutes: 30 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v3 diff --git a/packages/e2e-tests/.eslintrc.js b/packages/e2e-tests/.eslintrc.js index 8c585e48f252..5ba76262baaf 100644 --- a/packages/e2e-tests/.eslintrc.js +++ b/packages/e2e-tests/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { node: true, }, extends: ['../../.eslintrc.js'], - ignorePatterns: ['test-applications/**'], + ignorePatterns: ['test-applications/**', 'tmp/**'], parserOptions: { sourceType: 'module', }, diff --git a/packages/e2e-tests/.gitignore b/packages/e2e-tests/.gitignore index 4c49bd78f1d0..a16add542a3f 100644 --- a/packages/e2e-tests/.gitignore +++ b/packages/e2e-tests/.gitignore @@ -1 +1,2 @@ .env +tmp diff --git a/packages/e2e-tests/README.md b/packages/e2e-tests/README.md index 2a30943f52f6..d8227481a25a 100644 --- a/packages/e2e-tests/README.md +++ b/packages/e2e-tests/README.md @@ -54,7 +54,7 @@ To get you started with the recipe, you can copy the following into `test-recipe { "$schema": "../../test-recipe-schema.json", "testApplicationName": "My New Test Application", - "buildCommand": "yarn install --pure-lockfile", + "buildCommand": "yarn install --network-concurrency 1", "tests": [ { "testName": "My new test", diff --git a/packages/e2e-tests/lib/buildApp.ts b/packages/e2e-tests/lib/buildApp.ts new file mode 100644 index 000000000000..e802d691e96a --- /dev/null +++ b/packages/e2e-tests/lib/buildApp.ts @@ -0,0 +1,80 @@ +/* eslint-disable no-console */ + +import * as fs from 'fs-extra'; +import * as path from 'path'; + +import { DEFAULT_BUILD_TIMEOUT_SECONDS } from './constants'; +import type { Env, RecipeInstance } from './types'; +import { spawnAsync } from './utils'; + +export async function buildApp(appDir: string, recipeInstance: RecipeInstance, env: Env): Promise { + const { recipe, label, dependencyOverrides } = recipeInstance; + + const packageJsonPath = path.resolve(appDir, 'package.json'); + + if (dependencyOverrides) { + // Override dependencies + const packageJson: { dependencies?: Record } = JSON.parse( + fs.readFileSync(packageJsonPath, { encoding: 'utf-8' }), + ); + packageJson.dependencies = packageJson.dependencies + ? { ...packageJson.dependencies, ...dependencyOverrides } + : dependencyOverrides; + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), { + encoding: 'utf-8', + }); + } + + if (recipe.buildCommand) { + console.log(`Running build command for test application "${label}"`); + + const buildResult = await spawnAsync(recipe.buildCommand, { + cwd: appDir, + timeout: (recipe.buildTimeoutSeconds ?? DEFAULT_BUILD_TIMEOUT_SECONDS) * 1000, + env: { + ...process.env, + ...env, + } as unknown as NodeJS.ProcessEnv, + }); + + if (buildResult.error) { + console.log(`Build failed for test application "${label}"`); + + // Prepends some text to the output build command's output so we can distinguish it from logging in this script + console.log(buildResult.stdout.replace(/^/gm, ' [BUILD OUTPUT] ')); + console.log(buildResult.stderr.replace(/^/gm, ' [BUILD OUTPUT] ')); + + console.log('[BUILD ERROR] ', buildResult.error); + throw buildResult.error; + } + + if (recipe.buildAssertionCommand) { + console.log(`Running build assertion for test application "${label}"`); + + const buildAssertionResult = await spawnAsync( + recipe.buildAssertionCommand, + { + cwd: appDir, + timeout: (recipe.buildTimeoutSeconds ?? DEFAULT_BUILD_TIMEOUT_SECONDS) * 1000, + env: { + ...process.env, + ...env, + } as unknown as NodeJS.ProcessEnv, + }, + buildResult.stdout, + ); + + if (buildAssertionResult.error) { + console.log(`Build assertion failed for test application "${label}"`); + + // Prepends some text to the output build command's output so we can distinguish it from logging in this script + console.log(buildAssertionResult.stdout.replace(/^/gm, ' [BUILD ASSERTION OUTPUT] ')); + console.log(buildAssertionResult.stderr.replace(/^/gm, ' [BUILD ASSERTION OUTPUT] ')); + + console.log('[BUILD ASSERTION ERROR] ', buildAssertionResult.error); + + throw buildAssertionResult.error; + } + } + } +} diff --git a/packages/e2e-tests/lib/buildRecipeInstances.ts b/packages/e2e-tests/lib/buildRecipeInstances.ts new file mode 100644 index 000000000000..2820c2e055ce --- /dev/null +++ b/packages/e2e-tests/lib/buildRecipeInstances.ts @@ -0,0 +1,47 @@ +import * as fs from 'fs'; + +import type { Recipe, RecipeInput, RecipeInstance } from './types'; + +export function buildRecipeInstances(recipePaths: string[]): RecipeInstance[] { + const recipes = buildRecipes(recipePaths); + const recipeInstances: RecipeInstance[] = []; + + const basePort = 3001; + + recipes.forEach((recipe, i) => { + recipe.versions.forEach(version => { + const dependencyOverrides = + Object.keys(version.dependencyOverrides).length > 0 ? version.dependencyOverrides : undefined; + const dependencyOverridesInformationString = dependencyOverrides + ? ` (Dependency overrides: ${JSON.stringify(dependencyOverrides)})` + : ''; + + recipeInstances.push({ + label: `${recipe.testApplicationName}${dependencyOverridesInformationString}`, + recipe, + dependencyOverrides, + port: basePort + i, + }); + }); + }); + + return recipeInstances; +} + +function buildRecipes(recipePaths: string[]): Recipe[] { + return recipePaths.map(recipePath => buildRecipe(recipePath)); +} + +function buildRecipe(recipePath: string): Recipe { + const recipe: RecipeInput = JSON.parse(fs.readFileSync(recipePath, 'utf-8')); + + const versions = process.env.CANARY_E2E_TEST + ? recipe.canaryVersions ?? [] + : recipe.versions ?? [{ dependencyOverrides: {} }]; + + return { + ...recipe, + path: recipePath, + versions, + }; +} diff --git a/packages/e2e-tests/lib/constants.ts b/packages/e2e-tests/lib/constants.ts new file mode 100644 index 000000000000..fe24b98841fd --- /dev/null +++ b/packages/e2e-tests/lib/constants.ts @@ -0,0 +1,6 @@ +export const TEST_REGISTRY_CONTAINER_NAME = 'verdaccio-e2e-test-registry'; +export const DEFAULT_BUILD_TIMEOUT_SECONDS = 60 * 5; +export const DEFAULT_TEST_TIMEOUT_SECONDS = 60 * 2; +export const VERDACCIO_VERSION = '5.22.1'; +export const PUBLISH_PACKAGES_DOCKER_IMAGE_NAME = 'publish-packages'; +export const TMP_DIR = 'tmp'; diff --git a/packages/e2e-tests/lib/runAllTestApps.ts b/packages/e2e-tests/lib/runAllTestApps.ts new file mode 100644 index 000000000000..b12a0ccb2bdb --- /dev/null +++ b/packages/e2e-tests/lib/runAllTestApps.ts @@ -0,0 +1,84 @@ +/* eslint-disable no-console */ +import { buildRecipeInstances } from './buildRecipeInstances'; +import { buildAndTestApp } from './runTestApp'; +import type { RecipeInstance, RecipeTestResult } from './types'; + +export async function runAllTestApps( + recipePaths: string[], + envVarsToInject: Record, +): Promise { + const maxParallel = process.env.CI ? 2 : 5; + + const recipeInstances = buildRecipeInstances(recipePaths); + + const results = await shardPromises( + recipeInstances, + recipeInstance => buildAndTestApp(recipeInstance, envVarsToInject), + maxParallel, + ); + + console.log('--------------------------------------'); + console.log('Test Result Summary:'); + + results.forEach(result => { + if (result.buildFailed) { + console.log(`● BUILD FAILED - ${result.label} (${result.recipe.path}`); + } else { + console.log(`● BUILD SUCCEEDED - ${result.label}`); + result.tests.forEach(testResult => { + console.log(` ● ${testResult.result.padEnd(7, ' ')} ${testResult.testName}`); + }); + } + }); + + const failed = results.filter(result => result.buildFailed || result.testFailed); + + if (failed.length) { + console.log(`${failed.length} test(s) failed.`); + process.exit(1); + } + + console.log('All tests succeeded. 🎉'); +} + +// Always run X promises at a time +function shardPromises( + recipes: RecipeInstance[], + callback: (recipe: RecipeInstance) => Promise, + maxParallel: number, +): Promise { + return new Promise(resolve => { + console.log(`Running a total of ${recipes.length} jobs, with up to ${maxParallel} jobs in parallel...`); + const results: RecipeTestResult[] = []; + const remaining = recipes.slice(); + const running: Promise[] = []; + + function runNext(): void { + if (running.length < maxParallel && remaining.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const next = remaining.shift()!; + const promise = callback(next); + + console.log(`Running job ${next.label}, ${remaining.length} remaining...`); + + running.push(promise); + + promise + .then(result => results.push(result)) + .finally(() => { + const pos = running.indexOf(promise); + running.splice(pos, 1); + + runNext(); + }); + } else if (remaining.length === 0 && running.length === 0) { + resolve(results); + } + } + + // Initial runs + for (let i = 0; i < maxParallel; i++) { + runNext(); + } + }); +} diff --git a/packages/e2e-tests/lib/runTestApp.ts b/packages/e2e-tests/lib/runTestApp.ts new file mode 100644 index 000000000000..f38740fe5dd0 --- /dev/null +++ b/packages/e2e-tests/lib/runTestApp.ts @@ -0,0 +1,55 @@ +/* eslint-disable no-console */ + +import * as fs from 'fs-extra'; +import * as path from 'path'; + +import { buildApp } from './buildApp'; +import { TMP_DIR } from './constants'; +import { testApp } from './testApp'; +import type { Env, RecipeInstance, RecipeTestResult } from './types'; + +let tmpDirCount = 0; + +// This should never throw, we always return a result here +export async function buildAndTestApp( + recipeInstance: RecipeInstance, + envVarsToInject: Record, +): Promise { + const { recipe, port } = recipeInstance; + const recipeDirname = path.dirname(recipe.path); + + const targetDir = path.join(TMP_DIR, `${recipe.testApplicationName}-${tmpDirCount++}`); + + await fs.copy(recipeDirname, targetDir); + + const env: Env = { + ...envVarsToInject, + PORT: port.toString(), + }; + + try { + await buildApp(targetDir, recipeInstance, env); + } catch (error) { + await fs.remove(targetDir); + + return { + ...recipeInstance, + buildFailed: true, + testFailed: false, + tests: [], + }; + } + + // This cannot throw, we always return a result here + const results = await testApp(targetDir, recipeInstance, env); + + // Cleanup + await fs.remove(targetDir); + + return { + ...recipeInstance, + buildFailed: false, + testFailed: results.some(result => result.result !== 'PASS'), + tests: results, + }; +} diff --git a/packages/e2e-tests/lib/testApp.ts b/packages/e2e-tests/lib/testApp.ts new file mode 100644 index 000000000000..e25418662c38 --- /dev/null +++ b/packages/e2e-tests/lib/testApp.ts @@ -0,0 +1,50 @@ +/* eslint-disable no-console */ + +import { DEFAULT_TEST_TIMEOUT_SECONDS } from './constants'; +import type { Env, RecipeInstance, TestDef, TestResult } from './types'; +import { spawnAsync } from './utils'; + +export async function testApp(appDir: string, recipeInstance: RecipeInstance, env: Env): Promise { + const { recipe } = recipeInstance; + + const results: TestResult[] = []; + for (const test of recipe.tests) { + results.push(await runTest(appDir, recipeInstance, test, env)); + } + + return results; +} + +async function runTest(appDir: string, recipeInstance: RecipeInstance, test: TestDef, env: Env): Promise { + const { recipe, label } = recipeInstance; + console.log(`Running test command for test application "${label}", test "${test.testName}"`); + + const testResult = await spawnAsync(test.testCommand, { + cwd: appDir, + timeout: (recipe.testTimeoutSeconds ?? DEFAULT_TEST_TIMEOUT_SECONDS) * 1000, + env: { + ...process.env, + ...env, + } as unknown as NodeJS.ProcessEnv, + }); + + if (testResult.error) { + console.log(`Test failed for test application "${label}", test "${test.testName}"`); + + // Prepends some text to the output test command's output so we can distinguish it from logging in this script + console.log(testResult.stdout.replace(/^/gm, ' [TEST OUTPUT] ')); + console.log(testResult.stderr.replace(/^/gm, ' [TEST OUTPUT] ')); + + console.log('[TEST ERROR] ', testResult.error); + + return { + testName: test.testName, + result: testResult.error?.message.includes('ETDIMEDOUT') ? 'TIMEOUT' : 'FAIL', + }; + } + + return { + testName: test.testName, + result: 'PASS', + }; +} diff --git a/packages/e2e-tests/lib/types.ts b/packages/e2e-tests/lib/types.ts new file mode 100644 index 000000000000..c78bc45b6bf5 --- /dev/null +++ b/packages/e2e-tests/lib/types.ts @@ -0,0 +1,49 @@ +export type TestResult = { + testName: string; + result: 'PASS' | 'FAIL' | 'TIMEOUT'; +}; + +type DependencyOverrides = Record; + +export interface TestDef { + testName: string; + testCommand: string; + timeoutSeconds?: number; +} + +export interface RecipeInput { + testApplicationName: string; + buildCommand?: string; + buildAssertionCommand?: string; + buildTimeoutSeconds?: number; + testTimeoutSeconds?: number; + tests: TestDef[]; + versions?: { dependencyOverrides: DependencyOverrides }[]; + canaryVersions?: { dependencyOverrides: DependencyOverrides }[]; +} + +export interface Recipe { + path: string; + testApplicationName: string; + buildCommand?: string; + buildAssertionCommand?: string; + buildTimeoutSeconds?: number; + testTimeoutSeconds?: number; + tests: TestDef[]; + versions: { dependencyOverrides: DependencyOverrides }[]; +} + +export interface RecipeInstance { + label: string; + recipe: Recipe; + dependencyOverrides?: DependencyOverrides; + port: number; +} + +export interface RecipeTestResult extends RecipeInstance { + buildFailed: boolean; + testFailed: boolean; + tests: TestResult[]; +} + +export type Env = Record; diff --git a/packages/e2e-tests/lib/utils.ts b/packages/e2e-tests/lib/utils.ts new file mode 100644 index 000000000000..caf2cf424171 --- /dev/null +++ b/packages/e2e-tests/lib/utils.ts @@ -0,0 +1,69 @@ +/* eslint-disable no-console */ +import * as childProcess from 'child_process'; + +// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message +export function printCIErrorMessage(message: string): void { + if (process.env.CI) { + console.log(`::error::${message}`); + } else { + console.log(message); + } +} + +interface SpawnAsync { + stdout: string; + stderr: string; + error?: Error; + status: number | null; +} + +export function spawnAsync( + cmd: string, + options?: + | childProcess.SpawnOptionsWithoutStdio + | childProcess.SpawnOptionsWithStdioTuple, + input?: string, +): Promise { + const start = Date.now(); + + return new Promise(resolve => { + const cp = childProcess.spawn(cmd, { shell: true, ...options }); + + const stderr: unknown[] = []; + const stdout: string[] = []; + let error: Error | undefined; + + cp.stdout.on('data', data => { + stdout.push(data ? (data as object).toString() : ''); + }); + + cp.stderr.on('data', data => { + stderr.push(data ? (data as object).toString() : ''); + }); + + cp.on('error', e => { + error = e; + }); + + cp.on('close', status => { + const end = Date.now(); + + // We manually mark this as timed out if the process takes too long + if (!error && status === 1 && options?.timeout && end >= start + options.timeout) { + error = new Error(`ETDIMEDOUT: Process timed out after ${options.timeout} ms.`); + } + + resolve({ + stdout: stdout.join(''), + stderr: stderr.join(''), + error: error || (status !== 0 ? new Error(`Process exited with status ${status}`) : undefined), + status, + }); + }); + + if (input) { + cp.stdin.write(input); + cp.stdin.end(); + } + }); +} diff --git a/packages/e2e-tests/lib/validate.ts b/packages/e2e-tests/lib/validate.ts new file mode 100644 index 000000000000..2abb4974ce70 --- /dev/null +++ b/packages/e2e-tests/lib/validate.ts @@ -0,0 +1,33 @@ +/* eslint-disable no-console */ + +export function validate(): boolean { + let missingEnvVar = false; + + if (!process.env.E2E_TEST_AUTH_TOKEN) { + console.log( + "No auth token configured! Please configure the E2E_TEST_AUTH_TOKEN environment variable with an auth token that has the scope 'project:read'!", + ); + missingEnvVar = true; + } + + if (!process.env.E2E_TEST_DSN) { + console.log('No DSN configured! Please configure the E2E_TEST_DSN environment variable with a DSN!'); + missingEnvVar = true; + } + + if (!process.env.E2E_TEST_SENTRY_ORG_SLUG) { + console.log( + 'No Sentry organization slug configured! Please configure the E2E_TEST_SENTRY_ORG_SLUG environment variable with a Sentry organization slug!', + ); + missingEnvVar = true; + } + + if (!process.env.E2E_TEST_SENTRY_TEST_PROJECT) { + console.log( + 'No Sentry project configured! Please configure the E2E_TEST_SENTRY_TEST_PROJECT environment variable with a Sentry project slug!', + ); + missingEnvVar = true; + } + + return !missingEnvVar; +} diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 02b86527db17..73f13d1c5174 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -16,12 +16,14 @@ "test:e2e": "run-s test:validate-configuration test:validate-test-app-setups test:run", "test:run": "ts-node run.ts", "test:validate-configuration": "ts-node validate-verdaccio-configuration.ts", - "test:validate-test-app-setups": "ts-node validate-test-app-setups.ts" + "test:validate-test-app-setups": "ts-node validate-test-app-setups.ts", + "clean": "rimraf tmp test-applications/**/node_modules test-applications/**/dist" }, "devDependencies": { "@types/glob": "8.0.0", "@types/node": "^14.6.4", "dotenv": "16.0.3", + "fs-extra": "11.1.0", "glob": "8.0.3", "ts-node": "10.9.1", "typescript": "3.8.3", diff --git a/packages/e2e-tests/registrySetup.ts b/packages/e2e-tests/registrySetup.ts new file mode 100644 index 000000000000..ea7dc3decca5 --- /dev/null +++ b/packages/e2e-tests/registrySetup.ts @@ -0,0 +1,96 @@ +/* eslint-disable no-console */ +import * as childProcess from 'child_process'; +import * as path from 'path'; + +import { PUBLISH_PACKAGES_DOCKER_IMAGE_NAME, TEST_REGISTRY_CONTAINER_NAME, VERDACCIO_VERSION } from './lib/constants'; + +const publishScriptNodeVersion = process.env.E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION; +const repositoryRoot = path.resolve(__dirname, '../..'); + +// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines +function groupCIOutput(groupTitle: string, fn: () => void): void { + if (process.env.CI) { + console.log(`::group::${groupTitle}`); + fn(); + console.log('::endgroup::'); + } else { + fn(); + } +} + +export function registrySetup(): void { + groupCIOutput('Test Registry Setup', () => { + // Stop test registry container (Verdaccio) if it was already running + childProcess.spawnSync('docker', ['stop', TEST_REGISTRY_CONTAINER_NAME], { stdio: 'ignore' }); + console.log('Stopped previously running test registry'); + + // Start test registry (Verdaccio) + const startRegistryProcessResult = childProcess.spawnSync( + 'docker', + [ + 'run', + '--detach', + '--rm', + '--name', + TEST_REGISTRY_CONTAINER_NAME, + '-p', + '4873:4873', + '-v', + `${__dirname}/verdaccio-config:/verdaccio/conf`, + `verdaccio/verdaccio:${VERDACCIO_VERSION}`, + ], + { encoding: 'utf8', stdio: 'inherit' }, + ); + + if (startRegistryProcessResult.status !== 0) { + throw new Error('Start Registry Process failed.'); + } + + // Build container image that is uploading our packages to fake registry with specific Node.js/npm version + const buildPublishImageProcessResult = childProcess.spawnSync( + 'docker', + [ + 'build', + '--tag', + PUBLISH_PACKAGES_DOCKER_IMAGE_NAME, + '--file', + './Dockerfile.publish-packages', + ...(publishScriptNodeVersion ? ['--build-arg', `NODE_VERSION=${publishScriptNodeVersion}`] : []), + '.', + ], + { + encoding: 'utf8', + stdio: 'inherit', + }, + ); + + if (buildPublishImageProcessResult.status !== 0) { + throw new Error('Build Publish Image failed.'); + } + + // Run container that uploads our packages to fake registry + const publishImageContainerRunProcess = childProcess.spawnSync( + 'docker', + [ + 'run', + '--rm', + '-v', + `${repositoryRoot}:/sentry-javascript`, + '--network', + 'host', + PUBLISH_PACKAGES_DOCKER_IMAGE_NAME, + ], + { + encoding: 'utf8', + stdio: 'inherit', + }, + ); + + if (publishImageContainerRunProcess.status !== 0) { + throw new Error('Publish Image Container failed.'); + } + }); + + console.log(''); + console.log(''); +} diff --git a/packages/e2e-tests/run.ts b/packages/e2e-tests/run.ts index 842911863f02..a4fec2c480dc 100644 --- a/packages/e2e-tests/run.ts +++ b/packages/e2e-tests/run.ts @@ -1,441 +1,35 @@ /* eslint-disable max-lines */ /* eslint-disable no-console */ -import * as childProcess from 'child_process'; import * as dotenv from 'dotenv'; -import * as fs from 'fs'; import * as glob from 'glob'; -import * as path from 'path'; -// Load environment variables from .env file locally -dotenv.config(); +import { runAllTestApps } from './lib/runAllTestApps'; +import { validate } from './lib/validate'; +import { registrySetup } from './registrySetup'; -const repositoryRoot = path.resolve(__dirname, '../..'); +async function run(): Promise { + // Load environment variables from .env file locally + dotenv.config(); -const TEST_REGISTRY_CONTAINER_NAME = 'verdaccio-e2e-test-registry'; -const VERDACCIO_VERSION = '5.15.3'; - -const PUBLISH_PACKAGES_DOCKER_IMAGE_NAME = 'publish-packages'; - -const publishScriptNodeVersion = process.env.E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION; - -const DEFAULT_BUILD_TIMEOUT_SECONDS = 60 * 5; -const DEFAULT_TEST_TIMEOUT_SECONDS = 60 * 2; - -let missingEnvVar = false; - -if (!process.env.E2E_TEST_AUTH_TOKEN) { - console.log( - "No auth token configured! Please configure the E2E_TEST_AUTH_TOKEN environment variable with an auth token that has the scope 'project:read'!", - ); - missingEnvVar = true; -} - -if (!process.env.E2E_TEST_DSN) { - console.log('No DSN configured! Please configure the E2E_TEST_DSN environment variable with a DSN!'); - missingEnvVar = true; -} - -if (!process.env.E2E_TEST_SENTRY_ORG_SLUG) { - console.log( - 'No Sentry organization slug configured! Please configure the E2E_TEST_SENTRY_ORG_SLUG environment variable with a Sentry organization slug!', - ); - missingEnvVar = true; -} - -if (!process.env.E2E_TEST_SENTRY_TEST_PROJECT) { - console.log( - 'No Sentry project configured! Please configure the E2E_TEST_SENTRY_TEST_PROJECT environment variable with a Sentry project slug!', - ); - missingEnvVar = true; -} - -if (missingEnvVar) { - process.exit(1); -} - -const envVarsToInject = { - REACT_APP_E2E_TEST_DSN: process.env.E2E_TEST_DSN, - NEXT_PUBLIC_E2E_TEST_DSN: process.env.E2E_TEST_DSN, -}; - -// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines -function groupCIOutput(groupTitle: string, fn: () => void): void { - if (process.env.CI) { - console.log(`::group::${groupTitle}`); - fn(); - console.log('::endgroup::'); - } else { - fn(); - } -} - -// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message -function printCIErrorMessage(message: string): void { - if (process.env.CI) { - console.log(`::error::${message}`); - } else { - console.log(message); - } -} - -groupCIOutput('Test Registry Setup', () => { - // Stop test registry container (Verdaccio) if it was already running - childProcess.spawnSync('docker', ['stop', TEST_REGISTRY_CONTAINER_NAME], { stdio: 'ignore' }); - console.log('Stopped previously running test registry'); - - // Start test registry (Verdaccio) - const startRegistryProcessResult = childProcess.spawnSync( - 'docker', - [ - 'run', - '--detach', - '--rm', - '--name', - TEST_REGISTRY_CONTAINER_NAME, - '-p', - '4873:4873', - '-v', - `${__dirname}/verdaccio-config:/verdaccio/conf`, - `verdaccio/verdaccio:${VERDACCIO_VERSION}`, - ], - { encoding: 'utf8', stdio: 'inherit' }, - ); - - if (startRegistryProcessResult.status !== 0) { + if (!validate()) { process.exit(1); } - // Build container image that is uploading our packages to fake registry with specific Node.js/npm version - const buildPublishImageProcessResult = childProcess.spawnSync( - 'docker', - [ - 'build', - '--tag', - PUBLISH_PACKAGES_DOCKER_IMAGE_NAME, - '--file', - './Dockerfile.publish-packages', - ...(publishScriptNodeVersion ? ['--build-arg', `NODE_VERSION=${publishScriptNodeVersion}`] : []), - '.', - ], - { - encoding: 'utf8', - stdio: 'inherit', - }, - ); + const envVarsToInject = { + REACT_APP_E2E_TEST_DSN: process.env.E2E_TEST_DSN, + NEXT_PUBLIC_E2E_TEST_DSN: process.env.E2E_TEST_DSN, + }; - if (buildPublishImageProcessResult.status !== 0) { - process.exit(1); - } + try { + registrySetup(); - // Run container that uploads our packages to fake registry - const publishImageContainerRunProcess = childProcess.spawnSync( - 'docker', - [ - 'run', - '--rm', - '-v', - `${repositoryRoot}:/sentry-javascript`, - '--network', - 'host', - PUBLISH_PACKAGES_DOCKER_IMAGE_NAME, - ], - { - encoding: 'utf8', - stdio: 'inherit', - }, - ); + const recipePaths = glob.sync(`${__dirname}/test-applications/*/test-recipe.json`, { absolute: true }); - if (publishImageContainerRunProcess.status !== 0) { + await runAllTestApps(recipePaths, envVarsToInject); + } catch (error) { + console.error(error); process.exit(1); } -}); - -const recipePaths = glob.sync(`${__dirname}/test-applications/*/test-recipe.json`, { absolute: true }); - -let processShouldExitWithError = false; - -type TestResult = { - testName: string; - result: 'PASS' | 'FAIL' | 'TIMEOUT'; -}; - -type VersionResult = { - dependencyOverrides?: Record; - buildFailed: boolean; - testResults: TestResult[]; -}; - -type RecipeResult = { - testApplicationName: string; - testApplicationPath: string; - versionResults: VersionResult[]; -}; - -type Recipe = { - testApplicationName: string; - buildCommand?: string; - buildAssertionCommand?: string; - buildTimeoutSeconds?: number; - tests: { - testName: string; - testCommand: string; - timeoutSeconds?: number; - }[]; - versions?: { dependencyOverrides: Record }[]; - canaryVersions?: { dependencyOverrides: Record }[]; -}; - -const recipeResults: RecipeResult[] = recipePaths.map(recipePath => { - const recipe: Recipe = JSON.parse(fs.readFileSync(recipePath, 'utf-8')); - const recipeDirname = path.dirname(recipePath); - - function runRecipe(dependencyOverrides: Record | undefined): VersionResult { - const dependencyOverridesInformationString = dependencyOverrides - ? ` (Dependency overrides: ${JSON.stringify(dependencyOverrides)})` - : ''; - - if (recipe.buildCommand) { - console.log( - `Running E2E test build command for test application "${recipe.testApplicationName}"${dependencyOverridesInformationString}`, - ); - const buildCommandProcess = childProcess.spawnSync(recipe.buildCommand, { - cwd: path.dirname(recipePath), - encoding: 'utf8', - shell: true, // needed so we can pass the build command in as whole without splitting it up into args - timeout: (recipe.buildTimeoutSeconds ?? DEFAULT_BUILD_TIMEOUT_SECONDS) * 1000, - env: { - ...process.env, - ...envVarsToInject, - }, - }); - - // Prepends some text to the output build command's output so we can distinguish it from logging in this script - console.log(buildCommandProcess.stdout.replace(/^/gm, '[BUILD OUTPUT] ')); - console.log(buildCommandProcess.stderr.replace(/^/gm, '[BUILD OUTPUT] ')); - - const buildCommandProcessError: undefined | (Error & { code?: string }) = buildCommandProcess.error; - - if (buildCommandProcessError?.code === 'ETIMEDOUT') { - processShouldExitWithError = true; - - printCIErrorMessage( - `Build command in test application "${recipe.testApplicationName}" (${path.dirname(recipePath)}) timed out!`, - ); - - return { - dependencyOverrides, - buildFailed: true, - testResults: [], - }; - } else if (buildCommandProcess.status !== 0) { - processShouldExitWithError = true; - - printCIErrorMessage( - `Build command in test application "${recipe.testApplicationName}" (${path.dirname(recipePath)}) failed!`, - ); - - return { - dependencyOverrides, - buildFailed: true, - testResults: [], - }; - } - - if (recipe.buildAssertionCommand) { - console.log( - `Running E2E test build assertion for test application "${recipe.testApplicationName}"${dependencyOverridesInformationString}`, - ); - const buildAssertionCommandProcess = childProcess.spawnSync(recipe.buildAssertionCommand, { - cwd: path.dirname(recipePath), - input: buildCommandProcess.stdout, - encoding: 'utf8', - shell: true, // needed so we can pass the build command in as whole without splitting it up into args - timeout: (recipe.buildTimeoutSeconds ?? DEFAULT_BUILD_TIMEOUT_SECONDS) * 1000, - env: { - ...process.env, - ...envVarsToInject, - }, - }); - - // Prepends some text to the output build command's output so we can distinguish it from logging in this script - console.log(buildAssertionCommandProcess.stdout.replace(/^/gm, '[BUILD ASSERTION OUTPUT] ')); - console.log(buildAssertionCommandProcess.stderr.replace(/^/gm, '[BUILD ASSERTION OUTPUT] ')); - - const buildAssertionCommandProcessError: undefined | (Error & { code?: string }) = - buildAssertionCommandProcess.error; - - if (buildAssertionCommandProcessError?.code === 'ETIMEDOUT') { - processShouldExitWithError = true; - - printCIErrorMessage( - `Build assertion in test application "${recipe.testApplicationName}" (${path.dirname( - recipePath, - )}) timed out!`, - ); - - return { - dependencyOverrides, - buildFailed: true, - testResults: [], - }; - } else if (buildAssertionCommandProcess.status !== 0) { - processShouldExitWithError = true; - - printCIErrorMessage( - `Build assertion in test application "${recipe.testApplicationName}" (${path.dirname(recipePath)}) failed!`, - ); - - return { - dependencyOverrides, - buildFailed: true, - testResults: [], - }; - } - } - } - - const testResults: TestResult[] = recipe.tests.map(test => { - console.log( - `Running E2E test command for test application "${recipe.testApplicationName}", test "${test.testName}"${dependencyOverridesInformationString}`, - ); - - const testProcessResult = childProcess.spawnSync(test.testCommand, { - cwd: path.dirname(recipePath), - timeout: (test.timeoutSeconds ?? DEFAULT_TEST_TIMEOUT_SECONDS) * 1000, - encoding: 'utf8', - shell: true, // needed so we can pass the test command in as whole without splitting it up into args - env: { - ...process.env, - ...envVarsToInject, - }, - }); - - // Prepends some text to the output test command's output so we can distinguish it from logging in this script - console.log(testProcessResult.stdout.replace(/^/gm, '[TEST OUTPUT] ')); - console.log(testProcessResult.stderr.replace(/^/gm, '[TEST OUTPUT] ')); - - const error: undefined | (Error & { code?: string }) = testProcessResult.error; - - if (error?.code === 'ETIMEDOUT') { - processShouldExitWithError = true; - printCIErrorMessage( - `Test "${test.testName}" in test application "${recipe.testApplicationName}" (${path.dirname( - recipePath, - )}) timed out.`, - ); - return { - testName: test.testName, - result: 'TIMEOUT', - }; - } else if (testProcessResult.status !== 0) { - processShouldExitWithError = true; - printCIErrorMessage( - `Test "${test.testName}" in test application "${recipe.testApplicationName}" (${path.dirname( - recipePath, - )}) failed.`, - ); - return { - testName: test.testName, - result: 'FAIL', - }; - } else { - console.log( - `Test "${test.testName}" in test application "${recipe.testApplicationName}" (${path.dirname( - recipePath, - )}) succeeded.`, - ); - return { - testName: test.testName, - result: 'PASS', - }; - } - }); - - return { - dependencyOverrides, - buildFailed: false, - testResults, - }; - } - - const versionsToRun: { - dependencyOverrides?: Record; - }[] = process.env.CANARY_E2E_TEST ? recipe.canaryVersions ?? [] : recipe.versions ?? [{}]; - - const versionResults = versionsToRun.map(({ dependencyOverrides }) => { - const packageJsonPath = path.resolve(recipeDirname, 'package.json'); - const packageJsonBackupPath = path.resolve(recipeDirname, 'package.json.bak'); - - if (dependencyOverrides) { - // Back up original package.json - fs.copyFileSync(packageJsonPath, packageJsonBackupPath); - - // Override dependencies - const packageJson: { dependencies?: Record } = JSON.parse( - fs.readFileSync(packageJsonPath, { encoding: 'utf-8' }), - ); - packageJson.dependencies = packageJson.dependencies - ? { ...packageJson.dependencies, ...dependencyOverrides } - : dependencyOverrides; - fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), { - encoding: 'utf-8', - }); - } - - try { - return runRecipe(dependencyOverrides); - } finally { - if (dependencyOverrides) { - // Restore original package.json - fs.rmSync(packageJsonPath, { force: true }); - fs.copyFileSync(packageJsonBackupPath, packageJsonPath); - fs.rmSync(packageJsonBackupPath, { force: true }); - } - } - }); - - return { - testApplicationName: recipe.testApplicationName, - testApplicationPath: recipePath, - versionResults, - }; -}); - -console.log('--------------------------------------'); -console.log('Test Result Summary:'); - -recipeResults.forEach(recipeResult => { - recipeResult.versionResults.forEach(versionResult => { - const dependencyOverridesInformationString = versionResult.dependencyOverrides - ? ` (Dependency overrides: ${JSON.stringify(versionResult.dependencyOverrides)})` - : ''; - - if (versionResult.buildFailed) { - console.log( - `● BUILD FAILED - ${recipeResult.testApplicationName} (${path.dirname( - recipeResult.testApplicationPath, - )})${dependencyOverridesInformationString}`, - ); - } else { - console.log( - `● BUILD SUCCEEDED - ${recipeResult.testApplicationName} (${path.dirname( - recipeResult.testApplicationPath, - )})${dependencyOverridesInformationString}`, - ); - versionResult.testResults.forEach(testResult => { - console.log(` ● ${testResult.result.padEnd(7, ' ')} ${testResult.testName}`); - }); - } - }); -}); - -// Stop test registry -childProcess.spawnSync(`docker stop ${TEST_REGISTRY_CONTAINER_NAME}`, { encoding: 'utf8', stdio: 'ignore' }); -console.log('Successfully stopped test registry container'); // Output from command above is not good so we `ignore` it and emit our own - -if (processShouldExitWithError) { - console.log('Not all tests succeeded.'); - process.exit(1); -} else { - console.log('All tests succeeded.'); } + +void run(); diff --git a/packages/e2e-tests/test-applications/create-next-app/package.json b/packages/e2e-tests/test-applications/create-next-app/package.json index 5d5e829bace6..dee9275c2bdc 100644 --- a/packages/e2e-tests/test-applications/create-next-app/package.json +++ b/packages/e2e-tests/test-applications/create-next-app/package.json @@ -24,5 +24,9 @@ }, "devDependencies": { "@playwright/test": "^1.27.1" + }, + "volta": { + "node": "16.19.0", + "yarn": "1.22.19" } } diff --git a/packages/e2e-tests/test-applications/create-next-app/playwright.config.ts b/packages/e2e-tests/test-applications/create-next-app/playwright.config.ts index 289976304812..b2c8ace9d92d 100644 --- a/packages/e2e-tests/test-applications/create-next-app/playwright.config.ts +++ b/packages/e2e-tests/test-applications/create-next-app/playwright.config.ts @@ -29,8 +29,6 @@ const config: PlaywrightTestConfig = { use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', @@ -62,7 +60,7 @@ const config: PlaywrightTestConfig = { /* Run your local dev server before starting the tests */ webServer: { command: process.env.TEST_MODE === 'prod' ? 'yarn start' : 'yarn dev', - port: 3000, + port: process.env.PORT ? parseInt(process.env.PORT) : 3000, }, }; diff --git a/packages/e2e-tests/test-applications/create-next-app/test-recipe.json b/packages/e2e-tests/test-applications/create-next-app/test-recipe.json index cf4bf4431929..141f9e1489c1 100644 --- a/packages/e2e-tests/test-applications/create-next-app/test-recipe.json +++ b/packages/e2e-tests/test-applications/create-next-app/test-recipe.json @@ -1,7 +1,7 @@ { "$schema": "../../test-recipe-schema.json", "testApplicationName": "create-next-app", - "buildCommand": "yarn install --pure-lockfile && npx playwright install && yarn build", + "buildCommand": "yarn install --network-concurrency 1 && npx playwright install && yarn build", "tests": [ { "testName": "Playwright tests - Prod Mode", diff --git a/packages/e2e-tests/test-applications/create-next-app/tsconfig.json b/packages/e2e-tests/test-applications/create-next-app/tsconfig.json index 3ff0501fdb85..7686eb53f6ac 100644 --- a/packages/e2e-tests/test-applications/create-next-app/tsconfig.json +++ b/packages/e2e-tests/test-applications/create-next-app/tsconfig.json @@ -12,8 +12,7 @@ "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", - "incremental": true + "jsx": "preserve" }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.js"], "exclude": ["node_modules"] diff --git a/packages/e2e-tests/test-applications/create-react-app/package.json b/packages/e2e-tests/test-applications/create-react-app/package.json index 201f636468ad..e0fc502238d0 100644 --- a/packages/e2e-tests/test-applications/create-react-app/package.json +++ b/packages/e2e-tests/test-applications/create-react-app/package.json @@ -41,5 +41,9 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "volta": { + "node": "16.19.0", + "yarn": "1.22.19" } } diff --git a/packages/e2e-tests/test-applications/create-react-app/test-recipe.json b/packages/e2e-tests/test-applications/create-react-app/test-recipe.json index d21ed4a6ace1..3f3c496c4857 100644 --- a/packages/e2e-tests/test-applications/create-react-app/test-recipe.json +++ b/packages/e2e-tests/test-applications/create-react-app/test-recipe.json @@ -1,6 +1,6 @@ { "$schema": "../../test-recipe-schema.json", "testApplicationName": "create-react-app", - "buildCommand": "yarn install --pure-lockfile && yarn build", + "buildCommand": "yarn install --network-concurrency 1 && yarn build", "tests": [] } diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/package.json b/packages/e2e-tests/test-applications/nextjs-app-dir/package.json index 5256c1aa8cdb..064e5d396d1f 100644 --- a/packages/e2e-tests/test-applications/nextjs-app-dir/package.json +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/package.json @@ -24,5 +24,9 @@ "devDependencies": { "ts-node": "10.9.1", "@playwright/test": "^1.27.1" + }, + "volta": { + "node": "16.19.0", + "yarn": "1.22.19" } } diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/playwright.config.ts b/packages/e2e-tests/test-applications/nextjs-app-dir/playwright.config.ts index ae466dab4350..2bacdad88e2e 100644 --- a/packages/e2e-tests/test-applications/nextjs-app-dir/playwright.config.ts +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/playwright.config.ts @@ -7,6 +7,8 @@ if (!testEnv) { throw new Error('No test env defined'); } +const port = process.env.PORT ? parseInt(process.env.PORT) : 3000; + /** * See https://playwright.dev/docs/test-configuration. */ @@ -34,7 +36,7 @@ const config: PlaywrightTestConfig = { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ actionTimeout: 0, /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3000', + baseURL: `http://localhost:${port}`, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', @@ -54,7 +56,7 @@ const config: PlaywrightTestConfig = { webServer: [ { command: testEnv === 'development' ? 'yarn dev' : 'yarn start', - port: 3000, + port, }, { command: 'yarn ts-node-script start-event-proxy.ts', diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/test-recipe.json b/packages/e2e-tests/test-applications/nextjs-app-dir/test-recipe.json index b3ec72add40f..4f6290d444d0 100644 --- a/packages/e2e-tests/test-applications/nextjs-app-dir/test-recipe.json +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/test-recipe.json @@ -1,7 +1,7 @@ { "$schema": "../../test-recipe-schema.json", "testApplicationName": "nextjs-13-app-dir", - "buildCommand": "yarn install --pure-lockfile && npx playwright install && yarn build", + "buildCommand": "yarn install --network-concurrency 1 && npx playwright install && yarn build", "buildAssertionCommand": "yarn ts-node --script-mode assert-build.ts", "tests": [ { diff --git a/packages/e2e-tests/test-applications/nextjs-app-dir/tsconfig.json b/packages/e2e-tests/test-applications/nextjs-app-dir/tsconfig.json index bacd391b697e..e78ff0d9c0e7 100644 --- a/packages/e2e-tests/test-applications/nextjs-app-dir/tsconfig.json +++ b/packages/e2e-tests/test-applications/nextjs-app-dir/tsconfig.json @@ -13,7 +13,6 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "incremental": true, "plugins": [ { "name": "next" diff --git a/packages/e2e-tests/test-applications/node-express-app/package.json b/packages/e2e-tests/test-applications/node-express-app/package.json index 4c07eb8a1028..3bcdb79df6ba 100644 --- a/packages/e2e-tests/test-applications/node-express-app/package.json +++ b/packages/e2e-tests/test-applications/node-express-app/package.json @@ -19,5 +19,9 @@ }, "devDependencies": { "@playwright/test": "^1.27.1" + }, + "volta": { + "node": "16.19.0", + "yarn": "1.22.19" } } diff --git a/packages/e2e-tests/test-applications/node-express-app/playwright.config.ts b/packages/e2e-tests/test-applications/node-express-app/playwright.config.ts index d665bba2d978..ef7bf3704715 100644 --- a/packages/e2e-tests/test-applications/node-express-app/playwright.config.ts +++ b/packages/e2e-tests/test-applications/node-express-app/playwright.config.ts @@ -27,8 +27,6 @@ const config: PlaywrightTestConfig = { use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', @@ -60,7 +58,7 @@ const config: PlaywrightTestConfig = { /* Run your local dev server before starting the tests */ webServer: { command: 'yarn start', - port: 3000, + port: process.env.PORT ? parseInt(process.env.PORT) : 3000, }, }; diff --git a/packages/e2e-tests/test-applications/node-express-app/src/app.ts b/packages/e2e-tests/test-applications/node-express-app/src/app.ts index e128e6c33882..ec4a5c73fcb6 100644 --- a/packages/e2e-tests/test-applications/node-express-app/src/app.ts +++ b/packages/e2e-tests/test-applications/node-express-app/src/app.ts @@ -17,7 +17,7 @@ Sentry.init({ }); const app = express(); -const port = 3000; +const port = process.env.PORT ? parseInt(process.env.PORT) : 3000; app.use(Sentry.Handlers.requestHandler()); app.use(Sentry.Handlers.tracingHandler()); diff --git a/packages/e2e-tests/test-applications/node-express-app/test-recipe.json b/packages/e2e-tests/test-applications/node-express-app/test-recipe.json index e3038c8d0459..4859973cd360 100644 --- a/packages/e2e-tests/test-applications/node-express-app/test-recipe.json +++ b/packages/e2e-tests/test-applications/node-express-app/test-recipe.json @@ -1,7 +1,7 @@ { "$schema": "../../test-recipe-schema.json", - "testApplicationName": "Node Express App", - "buildCommand": "yarn install && yarn build", + "testApplicationName": "node-express-app", + "buildCommand": "yarn install --network-concurrency 1 && yarn build", "tests": [ { "testName": "Test express server", diff --git a/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/package.json b/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/package.json index cead7143f1ed..f1e97a01925a 100644 --- a/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/package.json +++ b/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/package.json @@ -46,5 +46,9 @@ "@playwright/test": "1.26.1", "axios": "1.1.2", "serve": "14.0.1" + }, + "volta": { + "node": "16.19.0", + "yarn": "1.22.19" } } diff --git a/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/playwright.config.ts b/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/playwright.config.ts index 9249280b4c34..1a6a49424672 100644 --- a/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/playwright.config.ts +++ b/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/playwright.config.ts @@ -29,8 +29,6 @@ const config: PlaywrightTestConfig = { use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', @@ -62,7 +60,7 @@ const config: PlaywrightTestConfig = { /* Run your local dev server before starting the tests */ webServer: { command: 'yarn start', - port: 3000, + port: process.env.PORT ? parseInt(process.env.PORT) : 3000, }, }; diff --git a/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/test-recipe.json b/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/test-recipe.json index 1a8f03c6bbae..fce2379a280f 100644 --- a/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/test-recipe.json +++ b/packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/test-recipe.json @@ -1,7 +1,7 @@ { "$schema": "../../test-recipe-schema.json", "testApplicationName": "standard-frontend-react-tracing-import", - "buildCommand": "yarn install --pure-lockfile && npx playwright install && yarn build", + "buildCommand": "yarn install --network-concurrency 1 && npx playwright install && yarn build", "tests": [ { "testName": "Playwright tests", diff --git a/packages/e2e-tests/test-applications/standard-frontend-react/package.json b/packages/e2e-tests/test-applications/standard-frontend-react/package.json index 13697319b0e6..d8f6db397dcd 100644 --- a/packages/e2e-tests/test-applications/standard-frontend-react/package.json +++ b/packages/e2e-tests/test-applications/standard-frontend-react/package.json @@ -45,5 +45,9 @@ "@playwright/test": "1.26.1", "axios": "1.1.2", "serve": "14.0.1" + }, + "volta": { + "node": "16.19.0", + "yarn": "1.22.19" } } diff --git a/packages/e2e-tests/test-applications/standard-frontend-react/playwright.config.ts b/packages/e2e-tests/test-applications/standard-frontend-react/playwright.config.ts index 9249280b4c34..1a6a49424672 100644 --- a/packages/e2e-tests/test-applications/standard-frontend-react/playwright.config.ts +++ b/packages/e2e-tests/test-applications/standard-frontend-react/playwright.config.ts @@ -29,8 +29,6 @@ const config: PlaywrightTestConfig = { use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', @@ -62,7 +60,7 @@ const config: PlaywrightTestConfig = { /* Run your local dev server before starting the tests */ webServer: { command: 'yarn start', - port: 3000, + port: process.env.PORT ? parseInt(process.env.PORT) : 3000, }, }; diff --git a/packages/e2e-tests/test-applications/standard-frontend-react/test-recipe.json b/packages/e2e-tests/test-applications/standard-frontend-react/test-recipe.json index 95ff8d535877..207dd5409b50 100644 --- a/packages/e2e-tests/test-applications/standard-frontend-react/test-recipe.json +++ b/packages/e2e-tests/test-applications/standard-frontend-react/test-recipe.json @@ -1,7 +1,7 @@ { "$schema": "../../test-recipe-schema.json", "testApplicationName": "standard-frontend-react", - "buildCommand": "yarn install --pure-lockfile && npx playwright install && yarn build", + "buildCommand": "yarn install --network-concurrency 1 && npx playwright install && yarn build", "tests": [ { "testName": "Playwright tests", diff --git a/packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts b/packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts index 6648bfd059a4..da5ba529edd7 100644 --- a/packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts +++ b/packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts @@ -2,7 +2,11 @@ import { expect } from '@playwright/test'; export const ReplayRecordingData = [ [ - { type: 4, data: { href: 'http://localhost:3000/', width: 1280, height: 720 }, timestamp: expect.any(Number) }, + { + type: 4, + data: { href: expect.stringMatching(/http:\/\/localhost:\d+\//), width: 1280, height: 720 }, + timestamp: expect.any(Number), + }, { type: 2, data: { @@ -105,7 +109,7 @@ export const ReplayRecordingData = [ node: { type: 2, tagName: 'a', - attributes: { id: 'navigation', href: 'http://localhost:3000/user/5' }, + attributes: { id: 'navigation', href: expect.stringMatching(/http:\/\/localhost:\d+\/user\/5/) }, childNodes: [], id: 14, }, @@ -140,7 +144,7 @@ export const ReplayRecordingData = [ tag: 'performanceSpan', payload: { op: 'navigation.navigate', - description: 'http://localhost:3000/', + description: expect.stringMatching(/http:\/\/localhost:\d+\//), startTimestamp: expect.any(Number), endTimestamp: expect.any(Number), data: { @@ -166,7 +170,7 @@ export const ReplayRecordingData = [ tag: 'performanceSpan', payload: { op: 'resource.script', - description: expect.stringMatching(/http:\/\/localhost:3000\/static\/js\/main.(\w+).js/), + description: expect.stringMatching(/http:\/\/localhost:\d+\/static\/js\/main.(\w+).js/), startTimestamp: expect.any(Number), endTimestamp: expect.any(Number), data: { diff --git a/packages/e2e-tests/test-recipe-schema.json b/packages/e2e-tests/test-recipe-schema.json index 178ca96ab3d0..ee14c2eb4f82 100644 --- a/packages/e2e-tests/test-recipe-schema.json +++ b/packages/e2e-tests/test-recipe-schema.json @@ -17,7 +17,11 @@ }, "buildTimeoutSeconds": { "type": "number", - "description": "Timeout for the build command in seconds. Default: 60" + "description": "Timeout for the build command in seconds. Default: 5 minutes" + }, + "testTimeoutSeconds": { + "type": "number", + "description": "Timeout for the test command in seconds. Default: 2 minutes" }, "tests": { "type": "array", diff --git a/packages/e2e-tests/verdaccio-config/config.yaml b/packages/e2e-tests/verdaccio-config/config.yaml index a5b4f7919cc8..7c5cd16df28d 100644 --- a/packages/e2e-tests/verdaccio-config/config.yaml +++ b/packages/e2e-tests/verdaccio-config/config.yaml @@ -204,7 +204,7 @@ middlewares: # https://verdaccio.org/docs/logger # log settings -logs: { type: stdout, format: pretty, level: http } +log: { type: stdout, format: pretty, level: http } #experiments: # # support for npm token command # token: false diff --git a/yarn.lock b/yarn.lock index 6726a721b75c..1e8f056d870b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13514,6 +13514,15 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-extra@11.1.0, fs-extra@^11.1.0: + version "11.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz#5784b102104433bb0e090f48bfc4a30742c357ed" + integrity sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" @@ -13552,15 +13561,6 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^11.1.0: - version "11.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz#5784b102104433bb0e090f48bfc4a30742c357ed" - integrity sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs-extra@^4.0.2, fs-extra@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"