Skip to content

Commit 0a51cf8

Browse files
authored
feat(test runner): config.tag for a global tag (#37846)
1 parent 676866d commit 0a51cf8

File tree

16 files changed

+164
-32
lines changed

16 files changed

+164
-32
lines changed

docs/src/test-api/class-fullconfig.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ Base directory for all relative paths used in the reporters.
112112

113113
See [`property: TestConfig.shard`].
114114

115+
## property: FullConfig.tags
116+
* since: v1.57
117+
- type: <[Array]<[string]>>
118+
119+
Resolved global tags. See [`property: TestConfig.tag`].
120+
115121
## property: FullConfig.updateSnapshots
116122
* since: v1.10
117123
- type: <[UpdateSnapshots]<"all"|"changed"|"missing"|"none">>

docs/src/test-api/class-testconfig.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,25 @@ export default defineConfig({
535535
```
536536

537537

538+
## property: TestConfig.tag
539+
* since: v1.57
540+
- type: ?<[string]|[Array]<[string]>>
541+
542+
Tag or tags prepended to each test in the report. Useful for tagging your test run to differentiate between [CI environments](../test-sharding.md#merging-reports-from-multiple-environments).
543+
544+
Note that each tag must start with `@` symbol. Learn more about [tagging](../test-annotations.md#tag-tests).
545+
546+
**Usage**
547+
548+
```js title="playwright.config.ts"
549+
import { defineConfig } from '@playwright/test';
550+
551+
export default defineConfig({
552+
tag: process.env.CI_ENVIRONMENT_NAME, // for example "@APIv2"
553+
});
554+
```
555+
556+
538557
## property: TestConfig.testDir
539558
* since: v1.10
540559
- type: ?<[string]>

docs/src/test-reporters-js.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,16 +263,50 @@ Blob reports contain all the details about the test run and can be used later to
263263
npx playwright test --reporter=blob
264264
```
265265

266-
By default, the report is written into the `blob-report` directory in the package.json directory or current working directory (if no package.json is found). The report file name looks like `report-<hash>.zip` or `report-<hash>-<shard_number>.zip` when [sharding](./test-sharding.md) is used. The hash is an optional value computed from `--grep`, `--grepInverted`, `--project` and file filters passed as command line arguments. The hash guarantees that running Playwright with different command line options will produce different but stable between runs report names. The output file name can be overridden in the configuration file or pass as `'PLAYWRIGHT_BLOB_OUTPUT_FILE'` environment variable.
266+
By default, the report is written into the `blob-report` directory in the package.json directory or current working directory (if no package.json is found).
267+
268+
The report file name looks like `report-<hash>.zip` or `report-<hash>-<shard_number>.zip` when [sharding](./test-sharding.md) is used. The hash is an optional value computed from `--grep`, `--grepInverted`, `--project`, [`property: TestConfig.tag`] and file filters passed as command line arguments. The hash guarantees that running Playwright with different command line options will produce different but stable between runs report names. The output file name can be overridden in the configuration file or passed as `'PLAYWRIGHT_BLOB_OUTPUT_FILE'` environment variable.
269+
270+
<Tabs
271+
groupId="blob-report"
272+
defaultValue="shards"
273+
values={[
274+
{label: 'Shards', value: 'shards'},
275+
{label: 'Environments', value: 'environments'},
276+
]
277+
}>
278+
279+
<TabItem value="shards">
280+
281+
When using blob report to merge multiple shards, you don't have to pass any options.
282+
283+
```js title="playwright.config.ts"
284+
import { defineConfig } from '@playwright/test';
285+
286+
export default defineConfig({
287+
reporter: 'blob',
288+
});
289+
```
290+
291+
</TabItem>
292+
293+
<TabItem value="environments">
294+
295+
When running tests in different environments, you might want to use [`property: TestConfig.tag`] to add a global tag corresponding to the environment. This tag will bring clarity to the merged report, and it will be used to produce a unique blob report name.
267296

268297
```js title="playwright.config.ts"
269298
import { defineConfig } from '@playwright/test';
270299

271300
export default defineConfig({
272-
reporter: [['blob', { outputFile: `./blob-report/report-${os.platform()}.zip` }]],
301+
reporter: 'blob',
302+
tag: process.env.CI_ENVIRONMENT_NAME, // for example "@APIv2" or "@linux"
273303
});
274304
```
275305

306+
</TabItem>
307+
308+
</Tabs>
309+
276310
Blob report supports following configuration options and environment variables:
277311

278312
| Environment Variable Name | Reporter Config Option| Description | Default

docs/src/test-sharding-js.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export default defineConfig({
5858
});
5959
```
6060

61-
Blob report contains information about all the tests that were run and their results as well as all test attachments such as traces and screenshot diffs. Blob reports can be merged and converted to any other Playwright report. By default, blob report will be generated into `blob-report` directory.
61+
Blob report contains information about all the tests that were run and their results as well as all test attachments such as traces and screenshot diffs. Blob reports can be merged and converted to any other Playwright report. By default, blob report will be generated into `blob-report` directory. You can learn about [blob report options here](./test-reporters.md#blob-reporter).
6262

6363
To merge reports from multiple shards, put the blob report files into a single directory, for example `all-blob-reports`. Blob report names contain shard number, so they will not clash.
6464

@@ -164,6 +164,22 @@ You can now see the reports have been merged and a combined HTML report is avail
164164
<img width="875" alt="image" src="https://github.com/microsoft/playwright/assets/9798949/b69dac59-fc19-4b98-8f49-814b1c29ca02" />
165165

166166

167+
## Merging reports from multiple environments
168+
169+
If you want to run the same tests in multiple environments, as opposed to shard your tests onto multiple machines, you need to differentiate these enviroments.
170+
171+
In this case, it is useful to specify the [`property: TestConfig.tag`] property, to tag all tests with the environment name. This tag will be automatically picked up by the blob report and later on by the merge tool.
172+
173+
```js title="playwright.config.ts"
174+
import { defineConfig } from '@playwright/test';
175+
176+
export default defineConfig({
177+
reporter: process.env.CI ? 'blob' : 'html',
178+
tag: process.env.CI_ENVIRONMENT_NAME, // for example "@APIv2"
179+
});
180+
```
181+
182+
167183
## Merge-reports CLI
168184

169185
`npx playwright merge-reports path/to/blob-reports-dir` reads all blob reports from the passed directory and merges them into a single report.

packages/playwright/src/common/config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ export class FullConfigInternal {
8989
// so that plugins such as gitCommitInfoPlugin can populate metadata once.
9090
userConfig.metadata = userConfig.metadata || {};
9191

92+
const globalTags = Array.isArray(userConfig.tag) ? userConfig.tag : (userConfig.tag ? [userConfig.tag] : []);
93+
for (const tag of globalTags) {
94+
if (tag[0] !== '@')
95+
throw new Error(`Tag must start with "@" symbol, got "${tag}" instead.`);
96+
}
97+
9298
this.config = {
9399
configFile: resolvedConfigFile,
94100
rootDir: pathResolve(configDir, userConfig.testDir) || configDir,
@@ -107,6 +113,7 @@ export class FullConfigInternal {
107113
quiet: takeFirst(configCLIOverrides.quiet, userConfig.quiet, false),
108114
projects: [],
109115
shard: takeFirst(configCLIOverrides.shard, userConfig.shard, null),
116+
tags: globalTags,
110117
updateSnapshots: takeFirst(configCLIOverrides.updateSnapshots, userConfig.updateSnapshots, 'missing'),
111118
updateSourceMethod: takeFirst(configCLIOverrides.updateSourceMethod, userConfig.updateSourceMethod, 'patch'),
112119
version: require('../../package.json').version,

packages/playwright/src/common/testLoader.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,21 @@ import { requireOrImport } from '../transform/transform';
2525
import { filterStackTrace } from '../util';
2626

2727
import type { TestError } from '../../types/testReporter';
28+
import type { FullConfigInternal } from './config';
2829

2930
export const defaultTimeout = 30000;
3031

3132
// To allow multiple loaders in the same process without clearing require cache,
3233
// we make these maps global.
3334
const cachedFileSuites = new Map<string, Suite>();
3435

35-
export async function loadTestFile(file: string, rootDir: string, testErrors?: TestError[]): Promise<Suite> {
36+
export async function loadTestFile(file: string, config: FullConfigInternal, testErrors?: TestError[]): Promise<Suite> {
3637
if (cachedFileSuites.has(file))
3738
return cachedFileSuites.get(file)!;
38-
const suite = new Suite(path.relative(rootDir, file) || path.basename(file), 'file');
39+
const suite = new Suite(path.relative(config.config.rootDir, file) || path.basename(file), 'file');
3940
suite._requireFile = file;
4041
suite.location = { file, line: 0, column: 0 };
42+
suite._tags = [...config.config.tags];
4143

4244
setCurrentlyLoadingFileSuite(suite);
4345
if (!isWorkerProcess()) {

packages/playwright/src/isomorphic/teleReceiver.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ export type JsonStackFrame = { file: string, line: number, column: number };
2525

2626
export type JsonStdIOType = 'stdout' | 'stderr';
2727

28-
export type JsonConfig = Pick<reporterTypes.FullConfig, 'configFile' | 'globalTimeout' | 'maxFailures' | 'metadata' | 'rootDir' | 'version' | 'workers' | 'globalSetup' | 'globalTeardown'>;
28+
export type JsonConfig = Pick<reporterTypes.FullConfig, 'configFile' | 'globalTimeout' | 'maxFailures' | 'metadata' | 'rootDir' | 'version' | 'workers' | 'globalSetup' | 'globalTeardown'> & {
29+
// optional for backwards compatibility
30+
tags?: reporterTypes.FullConfig['tags'],
31+
};
2932

3033
export type JsonPattern = {
3134
s?: string;
@@ -748,6 +751,7 @@ export const baseFullConfig: reporterTypes.FullConfig = {
748751
rootDir: '',
749752
quiet: false,
750753
shard: null,
754+
tags: [],
751755
updateSnapshots: 'missing',
752756
updateSourceMethod: 'patch',
753757
version: '',

packages/playwright/src/loader/loaderMain.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class LoaderMain extends ProcessRunner {
4444
async loadTestFile(params: { file: string }) {
4545
const testErrors: TestError[] = [];
4646
const config = await this._config();
47-
const fileSuite = await loadTestFile(params.file, config.config.rootDir, testErrors);
47+
const fileSuite = await loadTestFile(params.file, config, testErrors);
4848
this._poolBuilder.buildPools(fileSuite);
4949
return { fileSuite: fileSuite._deepSerialize(), testErrors };
5050
}

packages/playwright/src/reporters/merge.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type ReportData = {
3838
eventPatchers: JsonEventPatchers;
3939
reportFile: string;
4040
metadata: BlobReportMetadata;
41+
tags: string[];
4142
};
4243

4344
export async function createMergedReport(config: FullConfigInternal, dir: string, reporterDescriptions: ReporterDescription[], rootDirOverride: string | undefined) {
@@ -76,13 +77,15 @@ export async function createMergedReport(config: FullConfigInternal, dir: string
7677
};
7778

7879
await dispatchEvents(eventData.prologue);
79-
for (const { reportFile, eventPatchers, metadata } of eventData.reports) {
80+
for (const { reportFile, eventPatchers, metadata, tags } of eventData.reports) {
8081
const reportJsonl = await fs.promises.readFile(reportFile);
8182
const events = parseTestEvents(reportJsonl);
8283
new JsonStringInternalizer(stringPool).traverse(events);
8384
eventPatchers.patchers.push(new AttachmentPathPatcher(dir));
8485
if (metadata.name)
8586
eventPatchers.patchers.push(new GlobalErrorPatcher(metadata.name));
87+
if (tags.length)
88+
eventPatchers.patchers.push(new GlobalErrorPatcher(tags.join(' ')));
8689
eventPatchers.patchEvents(events);
8790
await dispatchEvents(events);
8891
}
@@ -219,20 +222,24 @@ async function mergeEvents(dir: string, shardReportFiles: string[], stringPool:
219222
eventPatchers.patchers.push(new PathSeparatorPatcher(metadata.pathSeparator));
220223
eventPatchers.patchEvents(parsedEvents);
221224

225+
let tags: string[] = [];
222226
for (const event of parsedEvents) {
223-
if (event.method === 'onConfigure')
227+
if (event.method === 'onConfigure') {
224228
configureEvents.push(event);
225-
else if (event.method === 'onProject')
229+
tags = event.params.config.tags || [];
230+
} else if (event.method === 'onProject') {
226231
projectEvents.push(event);
227-
else if (event.method === 'onEnd')
232+
} else if (event.method === 'onEnd') {
228233
endEvents.push(event);
234+
}
229235
}
230236

231237
// Save information about the reports to stream their test events later.
232238
reports.push({
233239
eventPatchers,
234240
reportFile: localPath,
235241
metadata,
242+
tags,
236243
});
237244
}
238245

packages/playwright/src/reporters/teleEmitter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export class TeleReporterEmitter implements ReporterV2 {
172172
workers: config.workers,
173173
globalSetup: config.globalSetup,
174174
globalTeardown: config.globalTeardown,
175+
tags: config.tags,
175176
};
176177
}
177178

0 commit comments

Comments
 (0)