chore: enforce unique project names
This commit is contained in:
parent
bbcc3c1238
commit
0d59dc4001
4
.github/workflows/tests_primary.yml
vendored
4
.github/workflows/tests_primary.yml
vendored
|
|
@ -55,7 +55,7 @@ jobs:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
||||||
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}
|
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}-*
|
||||||
- run: node tests/config/checkCoverage.js ${{ matrix.browser }}
|
- run: node tests/config/checkCoverage.js ${{ matrix.browser }}
|
||||||
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
||||||
if: always()
|
if: always()
|
||||||
|
|
@ -87,7 +87,7 @@ jobs:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npx playwright install --with-deps chromium-tip-of-tree
|
- run: npx playwright install --with-deps chromium-tip-of-tree
|
||||||
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=chromium
|
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=chromium-*
|
||||||
env:
|
env:
|
||||||
PWTEST_CHANNEL: chromium-tip-of-tree
|
PWTEST_CHANNEL: chromium-tip-of-tree
|
||||||
PWTEST_BOT_NAME: "${{ matrix.os }}-chromium-tip-of-tree"
|
PWTEST_BOT_NAME: "${{ matrix.os }}-chromium-tip-of-tree"
|
||||||
|
|
|
||||||
16
.github/workflows/tests_secondary.yml
vendored
16
.github/workflows/tests_secondary.yml
vendored
|
|
@ -42,7 +42,7 @@ jobs:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
||||||
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}
|
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}-*
|
||||||
- run: node tests/config/checkCoverage.js ${{ matrix.browser }}
|
- run: node tests/config/checkCoverage.js ${{ matrix.browser }}
|
||||||
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
||||||
if: always()
|
if: always()
|
||||||
|
|
@ -75,7 +75,7 @@ jobs:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
||||||
- run: npm run test -- --project=${{ matrix.browser }}
|
- run: npm run test -- --project=${{ matrix.browser }}-*
|
||||||
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
||||||
if: always()
|
if: always()
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
@ -106,10 +106,10 @@ jobs:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
||||||
- run: npm run test -- --project=${{ matrix.browser }} --workers=1
|
- run: npm run test -- --project=${{ matrix.browser }}-* --workers=1
|
||||||
if: matrix.browser == 'firefox'
|
if: matrix.browser == 'firefox'
|
||||||
shell: bash
|
shell: bash
|
||||||
- run: npm run test -- --project=${{ matrix.browser }}
|
- run: npm run test -- --project=${{ matrix.browser }}-*
|
||||||
if: matrix.browser != 'firefox'
|
if: matrix.browser != 'firefox'
|
||||||
shell: bash
|
shell: bash
|
||||||
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
||||||
|
|
@ -175,9 +175,9 @@ jobs:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
||||||
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }} --headed
|
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}-* --headed
|
||||||
if: always() && startsWith(matrix.os, 'ubuntu-')
|
if: always() && startsWith(matrix.os, 'ubuntu-')
|
||||||
- run: npm run test -- --project=${{ matrix.browser }} --headed
|
- run: npm run test -- --project=${{ matrix.browser }}-* --headed
|
||||||
if: always() && !startsWith(matrix.os, 'ubuntu-')
|
if: always() && !startsWith(matrix.os, 'ubuntu-')
|
||||||
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
||||||
if: always()
|
if: always()
|
||||||
|
|
@ -247,7 +247,7 @@ jobs:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium ${{ matrix.channel }}
|
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium ${{ matrix.channel }}
|
||||||
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}
|
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}-*
|
||||||
env:
|
env:
|
||||||
PWTEST_TRACE: 1
|
PWTEST_TRACE: 1
|
||||||
PWTEST_CHANNEL: ${{ matrix.channel }}
|
PWTEST_CHANNEL: ${{ matrix.channel }}
|
||||||
|
|
@ -868,7 +868,7 @@ jobs:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npx playwright install --with-deps chromium
|
- run: npx playwright install --with-deps chromium
|
||||||
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=chromium
|
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=chromium-*
|
||||||
env:
|
env:
|
||||||
PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW: 1
|
PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW: 1
|
||||||
- run: node tests/config/checkCoverage.js chromium
|
- run: node tests/config/checkCoverage.js chromium
|
||||||
|
|
|
||||||
2
.github/workflows/tests_service.yml
vendored
2
.github/workflows/tests_service.yml
vendored
|
|
@ -23,7 +23,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }} --workers=10 --retries=0
|
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}-* --workers=10 --retries=0
|
||||||
env:
|
env:
|
||||||
PWTEST_MODE: service2
|
PWTEST_MODE: service2
|
||||||
PWTEST_TRACE: 1
|
PWTEST_TRACE: 1
|
||||||
|
|
|
||||||
2
.github/workflows/tests_video.yml
vendored
2
.github/workflows/tests_video.yml
vendored
|
|
@ -32,7 +32,7 @@ jobs:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
|
||||||
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}
|
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}-*
|
||||||
env:
|
env:
|
||||||
PWTEST_VIDEO: 1
|
PWTEST_VIDEO: 1
|
||||||
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ctest": "playwright test --config=tests/library/playwright.config.ts --project=chromium",
|
"ctest": "playwright test --config=tests/library/playwright.config.ts --project=chromium-*",
|
||||||
"ftest": "playwright test --config=tests/library/playwright.config.ts --project=firefox",
|
"ftest": "playwright test --config=tests/library/playwright.config.ts --project=firefox-*",
|
||||||
"wtest": "playwright test --config=tests/library/playwright.config.ts --project=webkit",
|
"wtest": "playwright test --config=tests/library/playwright.config.ts --project=webkit-*",
|
||||||
"atest": "playwright test --config=tests/android/playwright.config.ts",
|
"atest": "playwright test --config=tests/android/playwright.config.ts",
|
||||||
"etest": "playwright test --config=tests/electron/playwright.config.ts",
|
"etest": "playwright test --config=tests/electron/playwright.config.ts",
|
||||||
"webview2test": "playwright test --config=tests/webview2/playwright.config.ts",
|
"webview2test": "playwright test --config=tests/webview2/playwright.config.ts",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { testCaseLabels } from './labelUtils';
|
|
||||||
import type { TestCaseSummary } from './types';
|
import type { TestCaseSummary } from './types';
|
||||||
|
|
||||||
export class Filter {
|
export class Filter {
|
||||||
|
|
@ -108,13 +107,13 @@ export class Filter {
|
||||||
if (test.outcome === 'skipped')
|
if (test.outcome === 'skipped')
|
||||||
status = 'skipped';
|
status = 'skipped';
|
||||||
const searchValues: SearchValues = {
|
const searchValues: SearchValues = {
|
||||||
text: (status + ' ' + test.projectName + ' ' + (test.botName || '') + ' ' + test.location.file + ' ' + test.path.join(' ') + ' ' + test.title).toLowerCase(),
|
text: (status + ' ' + test.projectName + ' ' + test.tags.join(' ') + ' ' + test.location.file + ' ' + test.path.join(' ') + ' ' + test.title).toLowerCase(),
|
||||||
project: test.projectName.toLowerCase(),
|
project: test.projectName.toLowerCase(),
|
||||||
status: status as any,
|
status: status as any,
|
||||||
file: test.location.file,
|
file: test.location.file,
|
||||||
line: String(test.location.line),
|
line: String(test.location.line),
|
||||||
column: String(test.location.column),
|
column: String(test.location.column),
|
||||||
labels: testCaseLabels(test).map(label => label.toLowerCase()),
|
labels: test.tags.map(tag => tag.toLowerCase()),
|
||||||
};
|
};
|
||||||
(test as any).searchValues = searchValues;
|
(test as any).searchValues = searchValues;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { TestCaseSummary } from './types';
|
|
||||||
|
|
||||||
const labelsSymbol = Symbol('labels');
|
|
||||||
|
|
||||||
// Note: all labels start with "@"
|
|
||||||
export function testCaseLabels(test: TestCaseSummary): string[] {
|
|
||||||
if (!(test as any)[labelsSymbol]) {
|
|
||||||
const labels: string[] = [];
|
|
||||||
if (test.botName)
|
|
||||||
labels.push('@' + test.botName);
|
|
||||||
labels.push(...test.tags);
|
|
||||||
(test as any)[labelsSymbol] = labels;
|
|
||||||
}
|
|
||||||
return (test as any)[labelsSymbol];
|
|
||||||
}
|
|
||||||
|
|
||||||
// hash string to integer in range [0, 6] for color index, to get same color for same tag
|
// hash string to integer in range [0, 6] for color index, to get same color for same tag
|
||||||
export function hashStringToInt(str: string) {
|
export function hashStringToInt(str: string) {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import { ProjectLink } from './links';
|
||||||
import { statusIcon } from './statusIcon';
|
import { statusIcon } from './statusIcon';
|
||||||
import './testCaseView.css';
|
import './testCaseView.css';
|
||||||
import { TestResultView } from './testResultView';
|
import { TestResultView } from './testResultView';
|
||||||
import { hashStringToInt, testCaseLabels } from './labelUtils';
|
import { hashStringToInt } from './labelUtils';
|
||||||
import { msToString } from './uiUtils';
|
import { msToString } from './uiUtils';
|
||||||
|
|
||||||
export const TestCaseView: React.FC<{
|
export const TestCaseView: React.FC<{
|
||||||
|
|
@ -37,7 +37,7 @@ export const TestCaseView: React.FC<{
|
||||||
const labels = React.useMemo(() => {
|
const labels = React.useMemo(() => {
|
||||||
if (!test)
|
if (!test)
|
||||||
return undefined;
|
return undefined;
|
||||||
return testCaseLabels(test);
|
return test.tags;
|
||||||
}, [test]);
|
}, [test]);
|
||||||
|
|
||||||
return <div className='test-case-column vbox'>
|
return <div className='test-case-column vbox'>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import { generateTraceUrl, Link, navigate, ProjectLink } from './links';
|
||||||
import { statusIcon } from './statusIcon';
|
import { statusIcon } from './statusIcon';
|
||||||
import './testFileView.css';
|
import './testFileView.css';
|
||||||
import { video, image, trace } from './icons';
|
import { video, image, trace } from './icons';
|
||||||
import { hashStringToInt, testCaseLabels } from './labelUtils';
|
import { hashStringToInt } from './labelUtils';
|
||||||
|
|
||||||
export const TestFileView: React.FC<React.PropsWithChildren<{
|
export const TestFileView: React.FC<React.PropsWithChildren<{
|
||||||
report: HTMLReport;
|
report: HTMLReport;
|
||||||
|
|
@ -52,7 +52,7 @@ export const TestFileView: React.FC<React.PropsWithChildren<{
|
||||||
</Link>
|
</Link>
|
||||||
{report.projectNames.length > 1 && !!test.projectName &&
|
{report.projectNames.length > 1 && !!test.projectName &&
|
||||||
<ProjectLink projectNames={report.projectNames} projectName={test.projectName} />}
|
<ProjectLink projectNames={report.projectNames} projectName={test.projectName} />}
|
||||||
<LabelsClickView labels={testCaseLabels(test)} />
|
<LabelsClickView labels={test.tags} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span data-testid='test-duration' style={{ minWidth: '50px', textAlign: 'right' }}>{msToString(test.duration)}</span>
|
<span data-testid='test-duration' style={{ minWidth: '50px', textAlign: 'right' }}>{msToString(test.duration)}</span>
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,6 @@ export type TestCaseSummary = {
|
||||||
title: string;
|
title: string;
|
||||||
path: string[];
|
path: string[];
|
||||||
projectName: string;
|
projectName: string;
|
||||||
botName?: string;
|
|
||||||
location: Location;
|
location: Location;
|
||||||
annotations: TestCaseAnnotation[];
|
annotations: TestCaseAnnotation[];
|
||||||
tags: string[];
|
tags: string[];
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ export class FullConfigInternal {
|
||||||
const projectConfigs = configCLIOverrides.projects || userConfig.projects || [userConfig];
|
const projectConfigs = configCLIOverrides.projects || userConfig.projects || [userConfig];
|
||||||
this.projects = projectConfigs.map(p => new FullProjectInternal(configDir, userConfig, this, p, this.configCLIOverrides, throwawayArtifactsPath));
|
this.projects = projectConfigs.map(p => new FullProjectInternal(configDir, userConfig, this, p, this.configCLIOverrides, throwawayArtifactsPath));
|
||||||
resolveProjectDependencies(this.projects);
|
resolveProjectDependencies(this.projects);
|
||||||
this._assignUniqueProjectIds(this.projects);
|
this._assignMissingProjectNames(this.projects);
|
||||||
setTransformConfig({
|
setTransformConfig({
|
||||||
babelPlugins: privateConfiguration?.babelPlugins || [],
|
babelPlugins: privateConfiguration?.babelPlugins || [],
|
||||||
external: userConfig.build?.external || [],
|
external: userConfig.build?.external || [],
|
||||||
|
|
@ -140,19 +140,21 @@ export class FullConfigInternal {
|
||||||
this.config.projects = this.projects.map(p => p.project);
|
this.config.projects = this.projects.map(p => p.project);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _assignUniqueProjectIds(projects: FullProjectInternal[]) {
|
private _assignMissingProjectNames(projects: FullProjectInternal[]) {
|
||||||
|
const userProvidedNames = new Set(projects.map(p => p.project.name));
|
||||||
const usedNames = new Set();
|
const usedNames = new Set();
|
||||||
|
let counter = 0;
|
||||||
for (const p of projects) {
|
for (const p of projects) {
|
||||||
const name = p.project.name || '';
|
if (p.project.name && usedNames.has(p.project.name))
|
||||||
for (let i = 0; i < projects.length; ++i) {
|
throw new Error(`Duplicate project name "${p.project.name}" in ${this.config.configFile}`);
|
||||||
const candidate = name + (i ? i : '');
|
|
||||||
if (usedNames.has(candidate))
|
if (!p.project.name && usedNames.has('')) {
|
||||||
continue;
|
let candidate = 'project_' + String(++counter);
|
||||||
p.id = candidate;
|
while (userProvidedNames.has(candidate))
|
||||||
(p.project as any).__projectId = p.id;
|
candidate = 'project_' + String(++counter);
|
||||||
usedNames.add(candidate);
|
p.project.name = candidate;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
usedNames.add(p.project.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,7 +166,6 @@ export class FullProjectInternal {
|
||||||
readonly expect: Project['expect'];
|
readonly expect: Project['expect'];
|
||||||
readonly respectGitIgnore: boolean;
|
readonly respectGitIgnore: boolean;
|
||||||
readonly snapshotPathTemplate: string;
|
readonly snapshotPathTemplate: string;
|
||||||
id = '';
|
|
||||||
deps: FullProjectInternal[] = [];
|
deps: FullProjectInternal[] = [];
|
||||||
teardown: FullProjectInternal | undefined;
|
teardown: FullProjectInternal | undefined;
|
||||||
|
|
||||||
|
|
@ -285,7 +286,3 @@ export const defaultGrep = /.*/;
|
||||||
export const defaultReporter = process.env.CI ? 'dot' : 'list';
|
export const defaultReporter = process.env.CI ? 'dot' : 'list';
|
||||||
|
|
||||||
const configInternalSymbol = Symbol('configInternalSymbol');
|
const configInternalSymbol = Symbol('configInternalSymbol');
|
||||||
|
|
||||||
export function getProjectId(project: FullProject): string {
|
|
||||||
return (project as any).__projectId!;
|
|
||||||
}
|
|
||||||
|
|
@ -62,7 +62,7 @@ export type WorkerInitParams = {
|
||||||
workerIndex: number;
|
workerIndex: number;
|
||||||
parallelIndex: number;
|
parallelIndex: number;
|
||||||
repeatEachIndex: number;
|
repeatEachIndex: number;
|
||||||
projectId: string;
|
projectName: string;
|
||||||
config: SerializedConfig;
|
config: SerializedConfig;
|
||||||
artifactsDir: string;
|
artifactsDir: string;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ export class PoolBuilder {
|
||||||
if (!this._testTypePools.has(testType)) {
|
if (!this._testTypePools.has(testType)) {
|
||||||
const optionOverrides = {
|
const optionOverrides = {
|
||||||
overrides: this._project?.project?.use ?? {},
|
overrides: this._project?.project?.use ?? {},
|
||||||
location: { file: `project#${this._project?.id}`, line: 1, column: 1 }
|
location: { file: `project#${this._project?.project.name}`, line: 1, column: 1 }
|
||||||
};
|
};
|
||||||
const pool = new FixturePool(testType.fixtures, e => this._handleLoadError(e, testErrors), undefined, undefined, optionOverrides);
|
const pool = new FixturePool(testType.fixtures, e => this._handleLoadError(e, testErrors), undefined, undefined, optionOverrides);
|
||||||
this._testTypePools.set(testType, pool);
|
this._testTypePools.set(testType, pool);
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,9 @@ export function bindFileSuiteToProject(project: FullProjectInternal, suite: Suit
|
||||||
suite._fileId = fileId;
|
suite._fileId = fileId;
|
||||||
// At the point of the query, suite is not yet attached to the project, so we only get file, describe and test titles.
|
// At the point of the query, suite is not yet attached to the project, so we only get file, describe and test titles.
|
||||||
const [file, ...titles] = test.titlePath();
|
const [file, ...titles] = test.titlePath();
|
||||||
const testIdExpression = `[project=${project.id}]${toPosixPath(file)}\x1e${titles.join('\x1e')}`;
|
const testIdExpression = `[project=${project.project.name}]${toPosixPath(file)}\x1e${titles.join('\x1e')}`;
|
||||||
const testId = fileId + '-' + calculateSha1(testIdExpression).slice(0, 20);
|
const testId = fileId + '-' + calculateSha1(testIdExpression).slice(0, 20);
|
||||||
test.id = testId;
|
test.id = testId;
|
||||||
test._projectId = project.id;
|
|
||||||
|
|
||||||
// Inherit properties from parent suites.
|
// Inherit properties from parent suites.
|
||||||
let inheritedRetries: number | undefined;
|
let inheritedRetries: number | undefined;
|
||||||
|
|
@ -80,7 +79,7 @@ export function bindFileSuiteToProject(project: FullProjectInternal, suite: Suit
|
||||||
|
|
||||||
// We only compute / set digest in the runner.
|
// We only compute / set digest in the runner.
|
||||||
if (test._poolDigest)
|
if (test._poolDigest)
|
||||||
test._workerHash = `${project.id}-${test._poolDigest}-0`;
|
test._workerHash = `${project.project.name}-${test._poolDigest}-0`;
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -91,13 +90,13 @@ export function applyRepeatEachIndex(project: FullProjectInternal, fileSuite: Su
|
||||||
fileSuite.forEachTest((test, suite) => {
|
fileSuite.forEachTest((test, suite) => {
|
||||||
if (repeatEachIndex) {
|
if (repeatEachIndex) {
|
||||||
const [file, ...titles] = test.titlePath();
|
const [file, ...titles] = test.titlePath();
|
||||||
const testIdExpression = `[project=${project.id}]${toPosixPath(file)}\x1e${titles.join('\x1e')} (repeat:${repeatEachIndex})`;
|
const testIdExpression = `[project=${project.project.name}]${toPosixPath(file)}\x1e${titles.join('\x1e')} (repeat:${repeatEachIndex})`;
|
||||||
const testId = suite._fileId + '-' + calculateSha1(testIdExpression).slice(0, 20);
|
const testId = suite._fileId + '-' + calculateSha1(testIdExpression).slice(0, 20);
|
||||||
test.id = testId;
|
test.id = testId;
|
||||||
test.repeatEachIndex = repeatEachIndex;
|
test.repeatEachIndex = repeatEachIndex;
|
||||||
|
|
||||||
if (test._poolDigest)
|
if (test._poolDigest)
|
||||||
test._workerHash = `${project.id}-${test._poolDigest}-${repeatEachIndex}`;
|
test._workerHash = `${project.project.name}-${test._poolDigest}-${repeatEachIndex}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,6 @@ export class TestCase extends Base implements reporterTypes.TestCase {
|
||||||
_pool: FixturePool | undefined;
|
_pool: FixturePool | undefined;
|
||||||
_poolDigest = '';
|
_poolDigest = '';
|
||||||
_workerHash = '';
|
_workerHash = '';
|
||||||
_projectId = '';
|
|
||||||
// Annotations known statically before running the test, e.g. `test.skip()` or `test(title, { annotation }, body)`.
|
// Annotations known statically before running the test, e.g. `test.skip()` or `test(title, { annotation }, body)`.
|
||||||
_staticAnnotations: Annotation[] = [];
|
_staticAnnotations: Annotation[] = [];
|
||||||
// Explicitly declared tags that are not a part of the title.
|
// Explicitly declared tags that are not a part of the title.
|
||||||
|
|
@ -314,7 +313,6 @@ export class TestCase extends Base implements reporterTypes.TestCase {
|
||||||
staticAnnotations: this._staticAnnotations.slice(),
|
staticAnnotations: this._staticAnnotations.slice(),
|
||||||
annotations: this.annotations.slice(),
|
annotations: this.annotations.slice(),
|
||||||
tags: this._tags.slice(),
|
tags: this._tags.slice(),
|
||||||
projectId: this._projectId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -331,7 +329,6 @@ export class TestCase extends Base implements reporterTypes.TestCase {
|
||||||
test._staticAnnotations = data.staticAnnotations;
|
test._staticAnnotations = data.staticAnnotations;
|
||||||
test.annotations = data.annotations;
|
test.annotations = data.annotations;
|
||||||
test._tags = data.tags;
|
test._tags = data.tags;
|
||||||
test._projectId = data.projectId;
|
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ export type JsonPattern = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type JsonProject = {
|
export type JsonProject = {
|
||||||
id: string;
|
|
||||||
grep: JsonPattern[];
|
grep: JsonPattern[];
|
||||||
grepInvert: JsonPattern[];
|
grepInvert: JsonPattern[];
|
||||||
metadata: Metadata;
|
metadata: Metadata;
|
||||||
|
|
@ -201,7 +200,7 @@ export class TeleReporterReceiver {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onProject(project: JsonProject) {
|
private _onProject(project: JsonProject) {
|
||||||
let projectSuite = this._rootSuite.suites.find(suite => suite.project()!.__projectId === project.id);
|
let projectSuite = this._rootSuite.suites.find(suite => suite.project()!.name === project.name);
|
||||||
if (!projectSuite) {
|
if (!projectSuite) {
|
||||||
projectSuite = new TeleSuite(project.name, 'project');
|
projectSuite = new TeleSuite(project.name, 'project');
|
||||||
this._rootSuite.suites.push(projectSuite);
|
this._rootSuite.suites.push(projectSuite);
|
||||||
|
|
@ -331,7 +330,6 @@ export class TeleReporterReceiver {
|
||||||
|
|
||||||
private _parseProject(project: JsonProject): TeleFullProject {
|
private _parseProject(project: JsonProject): TeleFullProject {
|
||||||
return {
|
return {
|
||||||
__projectId: project.id,
|
|
||||||
metadata: project.metadata,
|
metadata: project.metadata,
|
||||||
name: project.name,
|
name: project.name,
|
||||||
outputDir: this._absolutePath(project.outputDir),
|
outputDir: this._absolutePath(project.outputDir),
|
||||||
|
|
@ -593,7 +591,7 @@ class TeleTestResult implements reporterTypes.TestResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TeleFullProject = FullProject & { __projectId: string };
|
export type TeleFullProject = FullProject;
|
||||||
|
|
||||||
export const baseFullConfig: reporterTypes.FullConfig = {
|
export const baseFullConfig: reporterTypes.FullConfig = {
|
||||||
forbidOnly: false,
|
forbidOnly: false,
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ class HtmlBuilder {
|
||||||
}
|
}
|
||||||
const { testFile, testFileSummary } = fileEntry;
|
const { testFile, testFileSummary } = fileEntry;
|
||||||
const testEntries: TestEntry[] = [];
|
const testEntries: TestEntry[] = [];
|
||||||
this._processJsonSuite(fileSuite, fileId, projectSuite.project()!.name, projectSuite.project()!.metadata?.reportName, [], testEntries);
|
this._processJsonSuite(fileSuite, fileId, projectSuite.project()!.name, [], testEntries);
|
||||||
for (const test of testEntries) {
|
for (const test of testEntries) {
|
||||||
testFile.tests.push(test.testCase);
|
testFile.tests.push(test.testCase);
|
||||||
testFileSummary.tests.push(test.testCaseSummary);
|
testFileSummary.tests.push(test.testCaseSummary);
|
||||||
|
|
@ -341,13 +341,13 @@ class HtmlBuilder {
|
||||||
this._dataZipFile.addBuffer(Buffer.from(JSON.stringify(data)), fileName);
|
this._dataZipFile.addBuffer(Buffer.from(JSON.stringify(data)), fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _processJsonSuite(suite: Suite, fileId: string, projectName: string, botName: string | undefined, path: string[], outTests: TestEntry[]) {
|
private _processJsonSuite(suite: Suite, fileId: string, projectName: string, path: string[], outTests: TestEntry[]) {
|
||||||
const newPath = [...path, suite.title];
|
const newPath = [...path, suite.title];
|
||||||
suite.suites.forEach(s => this._processJsonSuite(s, fileId, projectName, botName, newPath, outTests));
|
suite.suites.forEach(s => this._processJsonSuite(s, fileId, projectName, newPath, outTests));
|
||||||
suite.tests.forEach(t => outTests.push(this._createTestEntry(fileId, t, projectName, botName, newPath)));
|
suite.tests.forEach(t => outTests.push(this._createTestEntry(fileId, t, projectName, newPath)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createTestEntry(fileId: string, test: TestCasePublic, projectName: string, botName: string | undefined, path: string[]): TestEntry {
|
private _createTestEntry(fileId: string, test: TestCasePublic, projectName: string, path: string[]): TestEntry {
|
||||||
const duration = test.results.reduce((a, r) => a + r.duration, 0);
|
const duration = test.results.reduce((a, r) => a + r.duration, 0);
|
||||||
const location = this._relativeLocation(test.location)!;
|
const location = this._relativeLocation(test.location)!;
|
||||||
path = path.slice(1);
|
path = path.slice(1);
|
||||||
|
|
@ -363,7 +363,6 @@ class HtmlBuilder {
|
||||||
testId,
|
testId,
|
||||||
title: test.title,
|
title: test.title,
|
||||||
projectName,
|
projectName,
|
||||||
botName,
|
|
||||||
location,
|
location,
|
||||||
duration,
|
duration,
|
||||||
// Annotations can be pushed directly, with a wrong type.
|
// Annotations can be pushed directly, with a wrong type.
|
||||||
|
|
@ -378,7 +377,6 @@ class HtmlBuilder {
|
||||||
testId,
|
testId,
|
||||||
title: test.title,
|
title: test.title,
|
||||||
projectName,
|
projectName,
|
||||||
botName,
|
|
||||||
location,
|
location,
|
||||||
duration,
|
duration,
|
||||||
// Annotations can be pushed directly, with a wrong type.
|
// Annotations can be pushed directly, with a wrong type.
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import path from 'path';
|
||||||
import type { FullConfig, TestCase, Suite, TestResult, TestError, TestStep, FullResult, Location, JSONReport, JSONReportSuite, JSONReportSpec, JSONReportTest, JSONReportTestResult, JSONReportTestStep, JSONReportError } from '../../types/testReporter';
|
import type { FullConfig, TestCase, Suite, TestResult, TestError, TestStep, FullResult, Location, JSONReport, JSONReportSuite, JSONReportSpec, JSONReportTest, JSONReportTestResult, JSONReportTestStep, JSONReportError } from '../../types/testReporter';
|
||||||
import { formatError, prepareErrorStack } from './base';
|
import { formatError, prepareErrorStack } from './base';
|
||||||
import { MultiMap, assert, toPosixPath } from 'playwright-core/lib/utils';
|
import { MultiMap, assert, toPosixPath } from 'playwright-core/lib/utils';
|
||||||
import { getProjectId } from '../common/config';
|
|
||||||
import EmptyReporter from './empty';
|
import EmptyReporter from './empty';
|
||||||
|
|
||||||
class JSONReporter extends EmptyReporter {
|
class JSONReporter extends EmptyReporter {
|
||||||
|
|
@ -64,7 +63,7 @@ class JSONReporter extends EmptyReporter {
|
||||||
repeatEach: project.repeatEach,
|
repeatEach: project.repeatEach,
|
||||||
retries: project.retries,
|
retries: project.retries,
|
||||||
metadata: project.metadata,
|
metadata: project.metadata,
|
||||||
id: getProjectId(project),
|
id: project.name,
|
||||||
name: project.name,
|
name: project.name,
|
||||||
testDir: toPosixPath(project.testDir),
|
testDir: toPosixPath(project.testDir),
|
||||||
testIgnore: serializePatterns(project.testIgnore),
|
testIgnore: serializePatterns(project.testIgnore),
|
||||||
|
|
@ -92,11 +91,10 @@ class JSONReporter extends EmptyReporter {
|
||||||
private _mergeSuites(suites: Suite[]): JSONReportSuite[] {
|
private _mergeSuites(suites: Suite[]): JSONReportSuite[] {
|
||||||
const fileSuites = new MultiMap<string, JSONReportSuite>();
|
const fileSuites = new MultiMap<string, JSONReportSuite>();
|
||||||
for (const projectSuite of suites) {
|
for (const projectSuite of suites) {
|
||||||
const projectId = getProjectId(projectSuite.project()!);
|
|
||||||
const projectName = projectSuite.project()!.name;
|
const projectName = projectSuite.project()!.name;
|
||||||
for (const fileSuite of projectSuite.suites) {
|
for (const fileSuite of projectSuite.suites) {
|
||||||
const file = fileSuite.location!.file;
|
const file = fileSuite.location!.file;
|
||||||
const serialized = this._serializeSuite(projectId, projectName, fileSuite);
|
const serialized = this._serializeSuite(projectName, fileSuite);
|
||||||
if (serialized)
|
if (serialized)
|
||||||
fileSuites.set(file, serialized);
|
fileSuites.set(file, serialized);
|
||||||
}
|
}
|
||||||
|
|
@ -153,35 +151,35 @@ class JSONReporter extends EmptyReporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _serializeSuite(projectId: string, projectName: string, suite: Suite): null | JSONReportSuite {
|
private _serializeSuite(projectName: string, suite: Suite): null | JSONReportSuite {
|
||||||
if (!suite.allTests().length)
|
if (!suite.allTests().length)
|
||||||
return null;
|
return null;
|
||||||
const suites = suite.suites.map(suite => this._serializeSuite(projectId, projectName, suite)).filter(s => s) as JSONReportSuite[];
|
const suites = suite.suites.map(suite => this._serializeSuite(projectName, suite)).filter(s => s) as JSONReportSuite[];
|
||||||
return {
|
return {
|
||||||
title: suite.title,
|
title: suite.title,
|
||||||
...this._relativeLocation(suite.location),
|
...this._relativeLocation(suite.location),
|
||||||
specs: suite.tests.map(test => this._serializeTestSpec(projectId, projectName, test)),
|
specs: suite.tests.map(test => this._serializeTestSpec(projectName, test)),
|
||||||
suites: suites.length ? suites : undefined,
|
suites: suites.length ? suites : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _serializeTestSpec(projectId: string, projectName: string, test: TestCase): JSONReportSpec {
|
private _serializeTestSpec(projectName: string, test: TestCase): JSONReportSpec {
|
||||||
return {
|
return {
|
||||||
title: test.title,
|
title: test.title,
|
||||||
ok: test.ok(),
|
ok: test.ok(),
|
||||||
tags: test.tags.map(tag => tag.substring(1)), // Strip '@'.
|
tags: test.tags.map(tag => tag.substring(1)), // Strip '@'.
|
||||||
tests: [this._serializeTest(projectId, projectName, test)],
|
tests: [this._serializeTest(projectName, test)],
|
||||||
id: test.id,
|
id: test.id,
|
||||||
...this._relativeLocation(test.location),
|
...this._relativeLocation(test.location),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _serializeTest(projectId: string, projectName: string, test: TestCase): JSONReportTest {
|
private _serializeTest(projectName: string, test: TestCase): JSONReportTest {
|
||||||
return {
|
return {
|
||||||
timeout: test.timeout,
|
timeout: test.timeout,
|
||||||
annotations: test.annotations,
|
annotations: test.annotations,
|
||||||
expectedStatus: test.expectedStatus,
|
expectedStatus: test.expectedStatus,
|
||||||
projectId,
|
projectId: projectName,
|
||||||
projectName,
|
projectName,
|
||||||
results: test.results.map(r => this._serializeTestResult(r, test)),
|
results: test.results.map(r => this._serializeTestResult(r, test)),
|
||||||
status: test.outcome(),
|
status: test.outcome(),
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import { TeleReporterReceiver } from '../isomorphic/teleReceiver';
|
||||||
import { JsonStringInternalizer, StringInternPool } from '../isomorphic/stringInternPool';
|
import { JsonStringInternalizer, StringInternPool } from '../isomorphic/stringInternPool';
|
||||||
import { createReporters } from '../runner/reporters';
|
import { createReporters } from '../runner/reporters';
|
||||||
import { Multiplexer } from './multiplexer';
|
import { Multiplexer } from './multiplexer';
|
||||||
import { ZipFile, calculateSha1 } from 'playwright-core/lib/utils';
|
import { ZipFile } from 'playwright-core/lib/utils';
|
||||||
import { currentBlobReportVersion, type BlobReportMetadata } from './blob';
|
import { currentBlobReportVersion, type BlobReportMetadata } from './blob';
|
||||||
import { relativeFilePath } from '../util';
|
import { relativeFilePath } from '../util';
|
||||||
import type { TestError } from '../../types/testReporter';
|
import type { TestError } from '../../types/testReporter';
|
||||||
|
|
@ -183,22 +183,15 @@ async function mergeEvents(dir: string, shardReportFiles: string[], stringPool:
|
||||||
return a.file.localeCompare(b.file);
|
return a.file.localeCompare(b.file);
|
||||||
});
|
});
|
||||||
|
|
||||||
const saltSet = new Set<string>();
|
|
||||||
|
|
||||||
printStatus(`merging events`);
|
printStatus(`merging events`);
|
||||||
|
|
||||||
const reports: ReportData[] = [];
|
const reports: ReportData[] = [];
|
||||||
|
|
||||||
for (const { file, parsedEvents, metadata, localPath } of blobs) {
|
for (let i = 0; i < blobs.length; ++i) {
|
||||||
// Generate unique salt for each blob.
|
// Generate unique salt for each blob.
|
||||||
const sha1 = calculateSha1(metadata.name || path.basename(file)).substring(0, 16);
|
const { parsedEvents, metadata, localPath } = blobs[i];
|
||||||
let salt = sha1;
|
|
||||||
for (let i = 0; saltSet.has(salt); i++)
|
|
||||||
salt = sha1 + '-' + i;
|
|
||||||
saltSet.add(salt);
|
|
||||||
|
|
||||||
const eventPatchers = new JsonEventPatchers();
|
const eventPatchers = new JsonEventPatchers();
|
||||||
eventPatchers.patchers.push(new IdsPatcher(stringPool, metadata.name, salt));
|
eventPatchers.patchers.push(new IdsPatcher(stringPool, metadata.name, String(i)));
|
||||||
// Only patch path separators if we are merging reports with explicit config.
|
// Only patch path separators if we are merging reports with explicit config.
|
||||||
if (rootDirOverride)
|
if (rootDirOverride)
|
||||||
eventPatchers.patchers.push(new PathSeparatorPatcher(metadata.pathSeparator));
|
eventPatchers.patchers.push(new PathSeparatorPatcher(metadata.pathSeparator));
|
||||||
|
|
@ -380,13 +373,17 @@ class IdsPatcher {
|
||||||
|
|
||||||
private _onProject(project: JsonProject) {
|
private _onProject(project: JsonProject) {
|
||||||
project.metadata = project.metadata ?? {};
|
project.metadata = project.metadata ?? {};
|
||||||
project.metadata.reportName = this._reportName;
|
|
||||||
project.id = this._stringPool.internString(project.id + this._salt);
|
|
||||||
project.suites.forEach(suite => this._updateTestIds(suite));
|
project.suites.forEach(suite => this._updateTestIds(suite));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateTestIds(suite: JsonSuite) {
|
private _updateTestIds(suite: JsonSuite) {
|
||||||
suite.tests.forEach(test => test.testId = this._mapTestId(test.testId));
|
suite.tests.forEach(test => {
|
||||||
|
test.testId = this._mapTestId(test.testId);
|
||||||
|
if (this._reportName) {
|
||||||
|
test.tags = test.tags || [];
|
||||||
|
test.tags.unshift('@' + this._reportName);
|
||||||
|
}
|
||||||
|
});
|
||||||
suite.suites.forEach(suite => this._updateTestIds(suite));
|
suite.suites.forEach(suite => this._updateTestIds(suite));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { createGuid } from 'playwright-core/lib/utils';
|
import { createGuid } from 'playwright-core/lib/utils';
|
||||||
import type { FullConfig, FullResult, Location, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter';
|
import type { FullConfig, FullResult, Location, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter';
|
||||||
import { getProjectId } from '../common/config';
|
|
||||||
import type { JsonAttachment, JsonConfig, JsonEvent, JsonFullResult, JsonProject, JsonStdIOType, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
import type { JsonAttachment, JsonConfig, JsonEvent, JsonFullResult, JsonProject, JsonStdIOType, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
||||||
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
||||||
import type { ReporterV2 } from './reporterV2';
|
import type { ReporterV2 } from './reporterV2';
|
||||||
|
|
@ -158,7 +157,6 @@ export class TeleReporterEmitter implements ReporterV2 {
|
||||||
private _serializeProject(suite: Suite): JsonProject {
|
private _serializeProject(suite: Suite): JsonProject {
|
||||||
const project = suite.project()!;
|
const project = suite.project()!;
|
||||||
const report: JsonProject = {
|
const report: JsonProject = {
|
||||||
id: getProjectId(project),
|
|
||||||
metadata: project.metadata,
|
metadata: project.metadata,
|
||||||
name: project.name,
|
name: project.name,
|
||||||
outputDir: this._relativePath(project.outputDir),
|
outputDir: this._relativePath(project.outputDir),
|
||||||
|
|
|
||||||
|
|
@ -180,9 +180,9 @@ export class Dispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
_createWorker(testGroup: TestGroup, parallelIndex: number, loaderData: SerializedConfig) {
|
_createWorker(testGroup: TestGroup, parallelIndex: number, loaderData: SerializedConfig) {
|
||||||
const projectConfig = this._config.projects.find(p => p.id === testGroup.projectId)!;
|
const projectConfig = this._config.projects.find(p => p.project.name === testGroup.projectName)!;
|
||||||
const outputDir = projectConfig.project.outputDir;
|
const outputDir = projectConfig.project.outputDir;
|
||||||
const worker = new WorkerHost(testGroup, parallelIndex, loaderData, this._extraEnvByProjectId.get(testGroup.projectId) || {}, outputDir);
|
const worker = new WorkerHost(testGroup, parallelIndex, loaderData, this._extraEnvByProjectId.get(testGroup.projectName) || {}, outputDir);
|
||||||
const handleOutput = (params: TestOutputPayload) => {
|
const handleOutput = (params: TestOutputPayload) => {
|
||||||
const chunk = chunkFromParams(params);
|
const chunk = chunkFromParams(params);
|
||||||
if (worker.didFail()) {
|
if (worker.didFail()) {
|
||||||
|
|
@ -212,8 +212,8 @@ export class Dispatcher {
|
||||||
this._reporter.onError(error);
|
this._reporter.onError(error);
|
||||||
});
|
});
|
||||||
worker.on('exit', () => {
|
worker.on('exit', () => {
|
||||||
const producedEnv = this._producedEnvByProjectId.get(testGroup.projectId) || {};
|
const producedEnv = this._producedEnvByProjectId.get(testGroup.projectName) || {};
|
||||||
this._producedEnvByProjectId.set(testGroup.projectId, { ...producedEnv, ...worker.producedEnv() });
|
this._producedEnvByProjectId.set(testGroup.projectName, { ...producedEnv, ...worker.producedEnv() });
|
||||||
});
|
});
|
||||||
return worker;
|
return worker;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -284,10 +284,10 @@ function createRunTestsTask(): Task<TestRun> {
|
||||||
// Inherit extra environment variables from dependencies.
|
// Inherit extra environment variables from dependencies.
|
||||||
let extraEnv: Record<string, string | undefined> = {};
|
let extraEnv: Record<string, string | undefined> = {};
|
||||||
for (const dep of project.deps)
|
for (const dep of project.deps)
|
||||||
extraEnv = { ...extraEnv, ...extraEnvByProjectId.get(dep.id) };
|
extraEnv = { ...extraEnv, ...extraEnvByProjectId.get(dep.project.name) };
|
||||||
for (const setup of teardownToSetups.get(project) || [])
|
for (const setup of teardownToSetups.get(project) || [])
|
||||||
extraEnv = { ...extraEnv, ...extraEnvByProjectId.get(setup.id) };
|
extraEnv = { ...extraEnv, ...extraEnvByProjectId.get(setup.project.name) };
|
||||||
extraEnvByProjectId.set(project.id, extraEnv);
|
extraEnvByProjectId.set(project.project.name, extraEnv);
|
||||||
|
|
||||||
const hasFailedDeps = project.deps.some(p => !successfulProjects.has(p));
|
const hasFailedDeps = project.deps.some(p => !successfulProjects.has(p));
|
||||||
if (!hasFailedDeps)
|
if (!hasFailedDeps)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export type TestGroup = {
|
||||||
workerHash: string;
|
workerHash: string;
|
||||||
requireFile: string;
|
requireFile: string;
|
||||||
repeatEachIndex: number;
|
repeatEachIndex: number;
|
||||||
projectId: string;
|
projectName: string;
|
||||||
tests: TestCase[];
|
tests: TestCase[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ export function createTestGroups(projectSuite: Suite, workers: number): TestGrou
|
||||||
workerHash: test._workerHash,
|
workerHash: test._workerHash,
|
||||||
requireFile: test._requireFile,
|
requireFile: test._requireFile,
|
||||||
repeatEachIndex: test.repeatEachIndex,
|
repeatEachIndex: test.repeatEachIndex,
|
||||||
projectId: test._projectId,
|
projectName: projectSuite.project()!.name,
|
||||||
tests: [],
|
tests: [],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export class WorkerHost extends ProcessHost {
|
||||||
workerIndex: this.workerIndex,
|
workerIndex: this.workerIndex,
|
||||||
parallelIndex,
|
parallelIndex,
|
||||||
repeatEachIndex: testGroup.repeatEachIndex,
|
repeatEachIndex: testGroup.repeatEachIndex,
|
||||||
projectId: testGroup.projectId,
|
projectName: testGroup.projectName,
|
||||||
config,
|
config,
|
||||||
artifactsDir: path.join(outputDir, artifactsFolderName(workerIndex))
|
artifactsDir: path.join(outputDir, artifactsFolderName(workerIndex))
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -167,8 +167,8 @@ export class TestInfoImpl implements TestInfo {
|
||||||
const fullTitleWithoutSpec = this.titlePath.slice(1).join(' ');
|
const fullTitleWithoutSpec = this.titlePath.slice(1).join(' ');
|
||||||
|
|
||||||
let testOutputDir = trimLongString(sanitizedRelativePath + '-' + sanitizeForFilePath(fullTitleWithoutSpec));
|
let testOutputDir = trimLongString(sanitizedRelativePath + '-' + sanitizeForFilePath(fullTitleWithoutSpec));
|
||||||
if (projectInternal.id)
|
if (projectInternal.project.name)
|
||||||
testOutputDir += '-' + sanitizeForFilePath(projectInternal.id);
|
testOutputDir += '-' + sanitizeForFilePath(projectInternal.project.name);
|
||||||
if (this.retry)
|
if (this.retry)
|
||||||
testOutputDir += '-retry' + this.retry;
|
testOutputDir += '-retry' + this.retry;
|
||||||
if (this.repeatEachIndex)
|
if (this.repeatEachIndex)
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ export class WorkerMain extends ProcessRunner {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._config = await deserializeConfig(this._params.config);
|
this._config = await deserializeConfig(this._params.config);
|
||||||
this._project = this._config.projects.find(p => p.id === this._params.projectId)!;
|
this._project = this._config.projects.find(p => p.project.name === this._params.projectName)!;
|
||||||
this._poolBuilder = PoolBuilder.createForWorker(this._project);
|
this._poolBuilder = PoolBuilder.createForWorker(this._project);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,23 +50,23 @@ const metadata = {
|
||||||
};
|
};
|
||||||
|
|
||||||
config.projects!.push({
|
config.projects!.push({
|
||||||
name: 'android',
|
name: 'android-native',
|
||||||
use: {
|
use: {
|
||||||
loopback: '10.0.2.2',
|
loopback: '10.0.2.2',
|
||||||
browserName: 'chromium',
|
browserName: 'chromium',
|
||||||
},
|
},
|
||||||
snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{ext}',
|
snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-android{ext}',
|
||||||
testDir: path.join(testDir, 'android'),
|
testDir: path.join(testDir, 'android'),
|
||||||
metadata,
|
metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
config.projects!.push({
|
config.projects!.push({
|
||||||
name: 'android',
|
name: 'android-page',
|
||||||
use: {
|
use: {
|
||||||
loopback: '10.0.2.2',
|
loopback: '10.0.2.2',
|
||||||
browserName: 'chromium',
|
browserName: 'chromium',
|
||||||
},
|
},
|
||||||
snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{ext}',
|
snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-android{ext}',
|
||||||
testDir: path.join(testDir, 'page'),
|
testDir: path.join(testDir, 'page'),
|
||||||
metadata,
|
metadata,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ const metadata = {
|
||||||
};
|
};
|
||||||
|
|
||||||
config.projects.push({
|
config.projects.push({
|
||||||
name: 'electron',
|
name: 'electron-api',
|
||||||
use: {
|
use: {
|
||||||
browserName: 'chromium',
|
browserName: 'chromium',
|
||||||
coverageName: 'electron',
|
coverageName: 'electron',
|
||||||
|
|
@ -61,7 +61,7 @@ config.projects.push({
|
||||||
});
|
});
|
||||||
|
|
||||||
config.projects.push({
|
config.projects.push({
|
||||||
name: 'electron',
|
name: 'electron-page',
|
||||||
// Share screenshots with chromium.
|
// Share screenshots with chromium.
|
||||||
snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-chromium{ext}',
|
snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-chromium{ext}',
|
||||||
use: {
|
use: {
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,10 @@ for (const browserName of browserNames) {
|
||||||
const testIgnore: RegExp[] = browserNames.filter(b => b !== browserName).map(b => new RegExp(b));
|
const testIgnore: RegExp[] = browserNames.filter(b => b !== browserName).map(b => new RegExp(b));
|
||||||
for (const folder of ['library', 'page']) {
|
for (const folder of ['library', 'page']) {
|
||||||
config.projects.push({
|
config.projects.push({
|
||||||
name: browserName,
|
name: `${browserName}-${folder}`,
|
||||||
testDir: path.join(testDir, folder),
|
testDir: path.join(testDir, folder),
|
||||||
testIgnore,
|
testIgnore,
|
||||||
snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{ext}',
|
snapshotPathTemplate: `{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-${browserName}{ext}`,
|
||||||
use: {
|
use: {
|
||||||
mode,
|
mode,
|
||||||
browserName,
|
browserName,
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,19 @@ test('should focus test from one project', async ({ runInlineTest }) => {
|
||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should throw on dupe project name', async ({ runInlineTest }) => {
|
||||||
|
const { exitCode, output } = await runInlineTest({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = { projects: [
|
||||||
|
{ name: 'a' },
|
||||||
|
{ name: 'a' },
|
||||||
|
] };
|
||||||
|
`,
|
||||||
|
}, { reporter: 'list,json' });
|
||||||
|
expect(output).toContain('Duplicate project name');
|
||||||
|
expect(exitCode).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('should work with default export', async ({ runInlineTest }) => {
|
test('should work with default export', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'file.spec.ts': `
|
'file.spec.ts': `
|
||||||
|
|
|
||||||
|
|
@ -350,29 +350,6 @@ test('should filter by project list, case-insensitive', async ({ runInlineTest }
|
||||||
expect(new Set(outputLines)).toEqual(new Set(['suite3', 'suite2']));
|
expect(new Set(outputLines)).toEqual(new Set(['suite3', 'suite2']));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should filter when duplicate project names exist', async ({ runInlineTest }) => {
|
|
||||||
const { passed, failed, outputLines, skipped } = await runInlineTest({
|
|
||||||
'playwright.config.ts': `
|
|
||||||
module.exports = { projects: [
|
|
||||||
{ name: 'suite1' },
|
|
||||||
{ name: 'suite2' },
|
|
||||||
{ name: 'suite1' },
|
|
||||||
{ name: 'suite4' },
|
|
||||||
] };
|
|
||||||
`,
|
|
||||||
'a.test.ts': `
|
|
||||||
import { test, expect } from '@playwright/test';
|
|
||||||
test('pass', async ({}, testInfo) => {
|
|
||||||
console.log('%%' + test.info().project.name);
|
|
||||||
});
|
|
||||||
`
|
|
||||||
}, { project: ['suite1', 'sUIte4'] });
|
|
||||||
expect(passed).toBe(3);
|
|
||||||
expect(failed).toBe(0);
|
|
||||||
expect(skipped).toBe(0);
|
|
||||||
expect(new Set(outputLines)).toEqual(new Set(['suite1', 'suite1', 'suite4']));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should print nice error when some of the projects are unknown', async ({ runInlineTest }) => {
|
test('should print nice error when some of the projects are unknown', async ({ runInlineTest }) => {
|
||||||
const { output, exitCode } = await runInlineTest({
|
const { output, exitCode } = await runInlineTest({
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
|
|
|
||||||
|
|
@ -388,7 +388,7 @@ test('should match snapshots from multiple projects', async ({ runInlineTest })
|
||||||
expect('Snapshot2').toMatchSnapshot('snapshot.txt');
|
expect('Snapshot2').toMatchSnapshot('snapshot.txt');
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
'p2/a.spec.js-snapshots/snapshot.txt': `Snapshot2`,
|
'p2/a.spec.js-snapshots/snapshot-project-1.txt': `Snapshot2`,
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1207,7 +1207,7 @@ test('preserve reportName on projects', async ({ runInlineTest, mergeReports })
|
||||||
onBegin(config, suite) {
|
onBegin(config, suite) {
|
||||||
const projects = suite.suites.map(s => s.project()).sort((a, b) => a.metadata.reportName.localeCompare(b.metadata.reportName));
|
const projects = suite.suites.map(s => s.project()).sort((a, b) => a.metadata.reportName.localeCompare(b.metadata.reportName));
|
||||||
console.log('projectNames: ' + projects.map(p => p.name));
|
console.log('projectNames: ' + projects.map(p => p.name));
|
||||||
console.log('reportNames: ' + projects.map(p => p.metadata.reportName));
|
suite.allTests().forEach(t => console.log('tags: ' + t.tags.join(' ')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = EchoReporter;
|
module.exports = EchoReporter;
|
||||||
|
|
@ -1232,8 +1232,9 @@ test('preserve reportName on projects', async ({ runInlineTest, mergeReports })
|
||||||
const reportDir = test.info().outputPath('blob-report');
|
const reportDir = test.info().outputPath('blob-report');
|
||||||
const { exitCode, output } = await mergeReports(reportDir, {}, { additionalArgs: ['--reporter', test.info().outputPath('echo-reporter.js')] });
|
const { exitCode, output } = await mergeReports(reportDir, {}, { additionalArgs: ['--reporter', test.info().outputPath('echo-reporter.js')] });
|
||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
expect(output).toContain(`projectNames: foo,foo`);
|
expect(output).toContain(`projectNames: foo`);
|
||||||
expect(output).toContain(`reportNames: first,second`);
|
expect(output).toContain(`tags: @first @smoke`);
|
||||||
|
expect(output).toContain(`tags: @second @smoke`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('no reports error', async ({ runInlineTest, mergeReports }) => {
|
test('no reports error', async ({ runInlineTest, mergeReports }) => {
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,6 @@ test('should include the project name', async ({ runInlineTest }) => {
|
||||||
module.exports = { projects: [
|
module.exports = { projects: [
|
||||||
{},
|
{},
|
||||||
{ name: 'foo' },
|
{ name: 'foo' },
|
||||||
{ name: 'foo' },
|
|
||||||
{ name: 'Bar space!' },
|
{ name: 'Bar space!' },
|
||||||
] };
|
] };
|
||||||
`,
|
`,
|
||||||
|
|
@ -181,18 +180,12 @@ test('should include the project name', async ({ runInlineTest }) => {
|
||||||
expect(result.output).toContain('test-results/my-test-test-1-retry1/bar.txt');
|
expect(result.output).toContain('test-results/my-test-test-1-retry1/bar.txt');
|
||||||
expect(result.output).toContain('my-test.spec.js-snapshots/bar.txt');
|
expect(result.output).toContain('my-test.spec.js-snapshots/bar.txt');
|
||||||
|
|
||||||
// test1, run with foo #1
|
// test1, run with foo
|
||||||
expect(result.output).toContain('test-results/my-test-test-1-foo/bar.txt');
|
expect(result.output).toContain('test-results/my-test-test-1-foo/bar.txt');
|
||||||
expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo.txt');
|
expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo.txt');
|
||||||
expect(result.output).toContain('test-results/my-test-test-1-foo-retry1/bar.txt');
|
expect(result.output).toContain('test-results/my-test-test-1-foo-retry1/bar.txt');
|
||||||
expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo.txt');
|
expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo.txt');
|
||||||
|
|
||||||
// test1, run with foo #2
|
|
||||||
expect(result.output).toContain('test-results/my-test-test-1-foo1/bar.txt');
|
|
||||||
expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo.txt');
|
|
||||||
expect(result.output).toContain('test-results/my-test-test-1-foo1-retry1/bar.txt');
|
|
||||||
expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo.txt');
|
|
||||||
|
|
||||||
// test1, run with bar
|
// test1, run with bar
|
||||||
expect(result.output).toContain('test-results/my-test-test-1-Bar-space-/bar.txt');
|
expect(result.output).toContain('test-results/my-test-test-1-Bar-space-/bar.txt');
|
||||||
expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space-.txt');
|
expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space-.txt');
|
||||||
|
|
@ -203,14 +196,10 @@ test('should include the project name', async ({ runInlineTest }) => {
|
||||||
expect(result.output).toContain('test-results/my-test-test-2/bar.txt');
|
expect(result.output).toContain('test-results/my-test-test-2/bar.txt');
|
||||||
expect(result.output).toContain('my-test.spec.js-snapshots/bar-suffix.txt');
|
expect(result.output).toContain('my-test.spec.js-snapshots/bar-suffix.txt');
|
||||||
|
|
||||||
// test2, run with foo #1
|
// test2, run with foo
|
||||||
expect(result.output).toContain('test-results/my-test-test-2-foo/bar.txt');
|
expect(result.output).toContain('test-results/my-test-test-2-foo/bar.txt');
|
||||||
expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo-suffix.txt');
|
expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo-suffix.txt');
|
||||||
|
|
||||||
// test2, run with foo #2
|
|
||||||
expect(result.output).toContain('test-results/my-test-test-2-foo1/bar.txt');
|
|
||||||
expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo-suffix.txt');
|
|
||||||
|
|
||||||
// test2, run with bar
|
// test2, run with bar
|
||||||
expect(result.output).toContain('test-results/my-test-test-2-Bar-space-/bar.txt');
|
expect(result.output).toContain('test-results/my-test-test-2-Bar-space-/bar.txt');
|
||||||
expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space--suffix.txt');
|
expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space--suffix.txt');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue