chore: remove config.groups (#17974)

This commit is contained in:
Yury Semikhatsky 2022-10-10 16:42:48 -07:00 committed by GitHub
parent f2dc1db7b6
commit 2d72d0ba03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 41 additions and 702 deletions

View File

@ -59,7 +59,6 @@ function addTestCommand(program: Command) {
command.option('--retries <retries>', `Maximum retry count for flaky tests, zero for no retries (default: no retries)`);
command.option('--shard <shard>', `Shard tests and execute only the selected shard, specify in the form "current/all", 1-based, for example "3/5"`);
command.option('--project <project-name...>', `Only run tests from the specified list of projects (default: run all projects)`);
command.option('--group <project-group-name>', `Only run tests from the specified project group (default: run all projects from the 'default' group or just all projects if 'default' group is not defined).`);
command.option('--timeout <timeout>', `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${defaultTimeout})`);
command.option('--trace <mode>', `Force tracing mode, can be ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`);
command.option('-u, --update-snapshots', `Update snapshots with actual results (default: only create missing snapshots)`);
@ -173,7 +172,6 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
testFileFilters,
testTitleMatcher,
projectFilter: opts.project || undefined,
projectGroup: opts.group,
passWithNoTests: opts.passWithNoTests,
});
await stopProfiling(undefined);

View File

@ -14,23 +14,22 @@
* limitations under the License.
*/
import { installTransform } from './transform';
import type { Config, Project, ReporterDescription, FullProjectInternal, FullConfigInternal, Fixtures, FixturesWithLocation } from './types';
import { getPackageJsonPath, mergeObjects, errorWithFile } from './util';
import { setCurrentlyLoadingFileSuite } from './globals';
import { Suite, type TestCase } from './test';
import type { SerializedLoaderData, WorkerIsolation } from './ipc';
import * as path from 'path';
import * as url from 'url';
import * as fs from 'fs';
import * as os from 'os';
import type { BuiltInReporter, ConfigCLIOverrides } from './runner';
import * as path from 'path';
import { calculateSha1, isRegExp } from 'playwright-core/lib/utils';
import * as url from 'url';
import type { Reporter } from '../types/testReporter';
import { builtInReporters } from './runner';
import { isRegExp, calculateSha1, isString, isObject } from 'playwright-core/lib/utils';
import { serializeError } from './util';
import { FixturePool, isFixtureOption } from './fixtures';
import { setCurrentlyLoadingFileSuite } from './globals';
import type { SerializedLoaderData, WorkerIsolation } from './ipc';
import type { BuiltInReporter, ConfigCLIOverrides } from './runner';
import { builtInReporters } from './runner';
import { Suite, type TestCase } from './test';
import type { TestTypeImpl } from './testType';
import { installTransform } from './transform';
import type { Config, Fixtures, FixturesWithLocation, FullConfigInternal, FullProjectInternal, Project, ReporterDescription } from './types';
import { errorWithFile, getPackageJsonPath, mergeObjects, serializeError } from './util';
export const defaultTimeout = 30000;
@ -165,10 +164,6 @@ export class Loader {
this._fullConfig.metadata = takeFirst(config.metadata, baseFullConfig.metadata);
this._fullConfig.projects = (config.projects || [config]).map(p => this._resolveProject(config, this._fullConfig, p, throwawayArtifactsPath));
this._assignUniqueProjectIds(this._fullConfig.projects);
// TODO: restore or remove once groups are decided upon.
this._fullConfig.groups = (config as any).groups;
validateProjectGroups(this._configFile || '<default config>', this._fullConfig);
}
private _assignUniqueProjectIds(projects: FullProjectInternal[]) {
@ -642,87 +637,6 @@ function validateProject(file: string, project: Project, title: string) {
}
}
function validateProjectGroups(file: string, config: FullConfigInternal) {
if (config.groups === undefined)
return;
const projectNames = new Set(config.projects?.filter(p => !!p.name).map(p => p.name));
for (const [groupName, group] of Object.entries(config.groups)) {
function validateProjectReference(projectName: string) {
if (projectName.trim() === '')
throw errorWithFile(file, `config.groups.${groupName} refers to an empty project name`);
if (!projectNames.has(projectName))
throw errorWithFile(file, `config.groups.${groupName} refers to an unknown project '${projectName}'`);
}
group.forEach((step, stepIndex) => {
if (isString(step)) {
validateProjectReference(step);
} else if (Array.isArray(step)) {
const parallelProjectNames = new Set();
step.forEach((item, itemIndex) => {
let projectName;
if (isString(item)) {
validateProjectReference(item);
projectName = item;
} else if (isObject(item)) {
const project = item.project;
if (isString(project)) {
validateProjectReference(project);
} else if (Array.isArray(project)) {
project.forEach((name, projectIndex) => {
if (!isString(name))
throw errorWithFile(file, `config.groups.${groupName}[${stepIndex}][${itemIndex}].project[${projectIndex}] contains non string value.`);
validateProjectReference(name);
});
}
projectName = project;
if ('grep' in item) {
if (Array.isArray(item.grep)) {
item.grep.forEach((item, grepIndex) => {
if (!isRegExp(item))
throw errorWithFile(file, `config.groups.${groupName}[${stepIndex}][${itemIndex}].grep[${grepIndex}] must be a RegExp`);
});
} else if (!isRegExp(item.grep)) {
throw errorWithFile(file, `config.groups.${groupName}[${stepIndex}][${itemIndex}].grep must be a RegExp`);
}
}
if ('grepInvert' in item) {
if (Array.isArray(item.grepInvert)) {
item.grepInvert.forEach((item, index) => {
if (!isRegExp(item))
throw errorWithFile(file, `config.groups.${groupName}[${stepIndex}][${itemIndex}].grepInvert[${index}] must be a RegExp`);
});
} else if (!isRegExp(item.grepInvert)) {
throw errorWithFile(file, `config.groups.${groupName}[${stepIndex}][${itemIndex}].grepInvert must be a RegExp`);
}
}
for (const prop of ['testIgnore', 'testMatch'] as const) {
if (prop in item) {
const value = item[prop];
if (Array.isArray(value)) {
value.forEach((item, index) => {
if (typeof item !== 'string' && !isRegExp(item))
throw errorWithFile(file, `config.groups.${groupName}[${stepIndex}][${itemIndex}].${prop}[${index}] must be a string or a RegExp`);
});
} else if (typeof value !== 'string' && !isRegExp(value)) {
throw errorWithFile(file, `config.groups.${groupName}[${stepIndex}][${itemIndex}].${prop} must be a string or a RegExp`);
}
}
}
} else {
throw errorWithFile(file, `config.groups.${groupName}[${stepIndex}][${itemIndex}] unexpected group entry ${JSON.stringify(step, null, 2)}`);
}
// We can relax this later.
if (parallelProjectNames.has(projectName))
throw errorWithFile(file, `config.groups.${groupName}[${stepIndex}][${itemIndex}] group mentions project '${projectName}' twice in one parallel group`);
parallelProjectNames.add(projectName);
});
} else {
throw errorWithFile(file, `config.groups.${groupName}[${stepIndex}] unexpected group entry ${JSON.stringify(step, null, 2)}`);
}
});
}
}
export const baseFullConfig: FullConfigInternal = {
forbidOnly: false,
fullyParallel: false,

View File

@ -15,39 +15,37 @@
* limitations under the License.
*/
import { rimraf, minimatch } from 'playwright-core/lib/utilsBundle';
import * as fs from 'fs';
import * as path from 'path';
import { assert } from 'playwright-core/lib/utils';
import { MultiMap } from 'playwright-core/lib/utils/multimap';
import { raceAgainstTimeout } from 'playwright-core/lib/utils/timeoutRunner';
import { colors, minimatch, rimraf } from 'playwright-core/lib/utilsBundle';
import { promisify } from 'util';
import type { FullResult, Reporter, TestError } from '../types/testReporter';
import type { TestGroup } from './dispatcher';
import { Dispatcher } from './dispatcher';
import type { Matcher, TestFileFilter } from './util';
import { createFileMatcher, createTitleMatcher, serializeError } from './util';
import type { TestCase } from './test';
import { Suite } from './test';
import { Loader } from './loader';
import type { FullResult, Reporter, TestError } from '../types/testReporter';
import { Multiplexer } from './reporters/multiplexer';
import { formatError } from './reporters/base';
import { colors } from 'playwright-core/lib/utilsBundle';
import DotReporter from './reporters/dot';
import GitHubReporter from './reporters/github';
import LineReporter from './reporters/line';
import ListReporter from './reporters/list';
import JSONReporter from './reporters/json';
import JUnitReporter from './reporters/junit';
import EmptyReporter from './reporters/empty';
import HtmlReporter from './reporters/html';
import type { Config, FullProjectInternal, ReporterInternal } from './types';
import type { FullConfigInternal } from './types';
import { raceAgainstTimeout } from 'playwright-core/lib/utils/timeoutRunner';
import { SigIntWatcher } from './sigIntWatcher';
import type { TestRunnerPlugin } from './plugins';
import { setRunnerToAddPluginsTo } from './plugins';
import { webServerPluginsForConfig } from './plugins/webServerPlugin';
import { dockerPlugin } from './plugins/dockerPlugin';
import { MultiMap } from 'playwright-core/lib/utils/multimap';
import { isString, assert } from 'playwright-core/lib/utils';
import { webServerPluginsForConfig } from './plugins/webServerPlugin';
import { formatError } from './reporters/base';
import DotReporter from './reporters/dot';
import EmptyReporter from './reporters/empty';
import GitHubReporter from './reporters/github';
import HtmlReporter from './reporters/html';
import JSONReporter from './reporters/json';
import JUnitReporter from './reporters/junit';
import LineReporter from './reporters/line';
import ListReporter from './reporters/list';
import { Multiplexer } from './reporters/multiplexer';
import { SigIntWatcher } from './sigIntWatcher';
import type { TestCase } from './test';
import { Suite } from './test';
import type { Config, FullConfigInternal, FullProjectInternal, ReporterInternal } from './types';
import type { Matcher, TestFileFilter } from './util';
import { createFileMatcher, createTitleMatcher, serializeError } from './util';
const removeFolderAsync = promisify(rimraf);
const readDirAsync = promisify(fs.readdir);
@ -63,66 +61,15 @@ type ProjectConstraints = {
// Project group is a sequence of run phases.
class RunPhase {
static collectRunPhases(options: RunOptions, config: FullConfigInternal): RunPhase[] {
let projectGroup = options.projectGroup;
if (options.projectFilter) {
if (projectGroup)
throw new Error('--group option can not be combined with --project');
} else {
if (!projectGroup && config.groups?.default && !options.testFileFilters?.length)
projectGroup = 'default';
if (projectGroup) {
if (config.shard)
throw new Error(`Project group '${projectGroup}' cannot be combined with --shard`);
}
}
const phases: RunPhase[] = [];
if (projectGroup) {
const group = config.groups?.[projectGroup];
if (!group)
throw new Error(`Cannot find project group '${projectGroup}' in the config`);
for (const entry of group) {
if (isString(entry)) {
phases.push(new RunPhase([{
projectName: entry,
testFileMatcher: () => true,
testTitleMatcher: () => true,
}]));
} else {
const phase: ProjectConstraints[] = [];
for (const p of entry) {
if (isString(p)) {
phase.push({
projectName: p,
testFileMatcher: () => true,
testTitleMatcher: () => true,
});
} else {
const testMatch = p.testMatch ? createFileMatcher(p.testMatch) : () => true;
const testIgnore = p.testIgnore ? createFileMatcher(p.testIgnore) : () => false;
const grep = p.grep ? createTitleMatcher(p.grep) : () => true;
const grepInvert = p.grepInvert ? createTitleMatcher(p.grepInvert) : () => false;
const projects = isString(p.project) ? [p.project] : p.project;
phase.push(...projects.map(projectName => ({
projectName,
testFileMatcher: (file: string) => !testIgnore(file) && testMatch(file),
testTitleMatcher: (title: string) => !grepInvert(title) && grep(title),
})));
}
}
phases.push(new RunPhase(phase));
}
}
} else {
const testFileMatcher = fileMatcherFrom(options.testFileFilters);
const testTitleMatcher = options.testTitleMatcher;
const projects = options.projectFilter ?? config.projects.map(p => p.name);
phases.push(new RunPhase(projects.map(projectName => ({
projectName,
testFileMatcher,
testTitleMatcher
}))));
}
const testFileMatcher = fileMatcherFrom(options.testFileFilters);
const testTitleMatcher = options.testTitleMatcher;
const projects = options.projectFilter ?? config.projects.map(p => p.name);
phases.push(new RunPhase(projects.map(projectName => ({
projectName,
testFileMatcher,
testTitleMatcher
}))));
return phases;
}
@ -152,7 +99,6 @@ type RunOptions = {
testFileFilters: TestFileFilter[];
testTitleMatcher: Matcher;
projectFilter?: string[];
projectGroup?: string;
passWithNoTests?: boolean;
};
@ -455,7 +401,6 @@ export class Runner {
// Compute shards.
const shard = config.shard;
if (shard) {
assert(!options.projectGroup);
assert(concurrentTestGroups.length === 1);
const shardGroups: TestGroup[] = [];
const shardTests = new Set<TestCase>();

View File

@ -55,14 +55,6 @@ export interface FullConfigInternal extends FullConfigPublic {
// Overrides the public field.
projects: FullProjectInternal[];
groups?: { [key: string]: Array<string | Array<string | {
project: string | string[],
grep?: RegExp | RegExp[],
grepInvert?: RegExp | RegExp[],
testMatch?: string | RegExp | Array<string | RegExp>,
testIgnore?: string | RegExp | Array<string | RegExp>
}>> };
}
/**

View File

@ -480,147 +480,3 @@ test('should have correct types for the config', async ({ runTSC }) => {
});
expect(result.exitCode).toBe(0);
});
test('should throw when group has duplicate project references', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
projects: [
{ name: 'a' },
],
groups: {
default: [
['a', 'a']
]
}
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async () => {});
`
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain(`config.groups.default[0][1] group mentions project 'a' twice in one parallel group`);
});
test('should throw when group grep has invalid type', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
projects: [
{ name: 'a' },
],
groups: {
default: [
[{ project: 'a', grep: 2022 }]
]
}
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async () => {});
`
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain(`config.groups.default[0][0].grep must be a RegExp`);
});
test('should throw when group grepInvert has invalid type', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
projects: [
{ name: 'a' },
],
groups: {
default: [
[{ project: 'a', grepInvert: [{}] }]
]
}
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async () => {});
`
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain(`config.groups.default[0][0].grepInvert[0] must be a RegExp`);
});
test('should throw when group testMatch has invalid type', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
projects: [
{ name: 'a' },
],
groups: {
all: [
[{ project: 'a', testMatch: [{}] }]
]
}
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async () => {});
`
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain(`config.groups.all[0][0].testMatch[0] must be a string or a RegEx`);
});
test('should throw when group testIgnore has invalid type', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
projects: [
{ name: 'a' },
],
groups: {
all: [
[{ project: 'a', testIgnore: [2022] }]
]
}
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async () => {});
`
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain(`config.groups.all[0][0].testIgnore[0] must be a string or a RegEx`);
});
test('should throw when group has unknown project reference', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
projects: [
{ name: 'a' },
],
groups: {
default: [
[{project: 'b'}]
]
}
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async () => {});
`
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain(`config.groups.default refers to an unknown project 'b'`);
});

View File

@ -1,366 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { PlaywrightTestConfig, TestInfo, PlaywrightTestProject } from '@playwright/test';
import path from 'path';
import { test, expect } from './playwright-test-fixtures';
function createConfigWithProjects(names: string[], testInfo: TestInfo, groups: PlaywrightTestConfig['groups'], projectTemplates?: { [name: string]: PlaywrightTestProject }): Record<string, string> {
const config: PlaywrightTestConfig = {
projects: names.map(name => ({ ...projectTemplates?.[name], name, testDir: testInfo.outputPath(name) })),
groups
};
const files = {};
for (const name of names) {
files[`${name}/${name}.spec.ts`] = `
const { test } = pwt;
test('${name} test', async () => {
await new Promise(f => setTimeout(f, 100));
});`;
}
function replacer(key, value) {
if (value instanceof RegExp)
return `RegExp(${value.toString()})`;
else
return value;
}
files['playwright.config.ts'] = `
import * as path from 'path';
module.exports = ${JSON.stringify(config, replacer, 2)};
`.replace(/"RegExp\((.*)\)"/g, '$1');
return files;
}
type Timeline = { titlePath: string[], event: 'begin' | 'end' }[];
function formatTimeline(timeline: Timeline) {
return timeline.map(e => `${e.titlePath.slice(1).join(' > ')} [${e.event}]`).join('\n');
}
function projectNames(timeline: Timeline) {
const projectNames = Array.from(new Set(timeline.map(({ titlePath }) => titlePath[1])).keys());
projectNames.sort();
return projectNames;
}
function expectRunBefore(timeline: Timeline, before: string[], after: string[]) {
const begin = new Map<string, number>();
const end = new Map<string, number>();
for (let i = 0; i < timeline.length; i++) {
const projectName = timeline[i].titlePath[1];
const map = timeline[i].event === 'begin' ? begin : end;
const oldIndex = map.get(projectName) ?? i;
const newIndex = (timeline[i].event === 'begin') ? Math.min(i, oldIndex) : Math.max(i, oldIndex);
map.set(projectName, newIndex);
}
for (const b of before) {
for (const a of after) {
const bEnd = end.get(b) as number;
expect(bEnd === undefined, `Unknown project ${b}`).toBeFalsy();
const aBegin = begin.get(a) as number;
expect(aBegin === undefined, `Unknown project ${a}`).toBeFalsy();
if (bEnd < aBegin)
continue;
throw new Error(`Project '${b}' expected to finish before '${a}'\nTest run order was:\n${formatTimeline(timeline)}`);
}
}
}
test('should work', async ({ runGroups }, testInfo) => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: ['a']
});
const { exitCode, passed, timeline } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expect(passed).toBe(1);
expect(formatTimeline(timeline)).toEqual(`a > a${path.sep}a.spec.ts > a test [begin]
a > a${path.sep}a.spec.ts > a test [end]`);
});
test('should order two projects', async ({ runGroups }, testInfo) => {
await test.step(`order a then b`, async () => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
'a',
'b'
]
});
const { exitCode, passed, timeline } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expect(passed).toBe(2);
expect(formatTimeline(timeline)).toEqual(`a > a${path.sep}a.spec.ts > a test [begin]
a > a${path.sep}a.spec.ts > a test [end]
b > b${path.sep}b.spec.ts > b test [begin]
b > b${path.sep}b.spec.ts > b test [end]`);
});
await test.step(`order b then a`, async () => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
'b',
'a'
]
});
const { exitCode, passed, timeline } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expect(passed).toBe(2);
expect(formatTimeline(timeline)).toEqual(`b > b${path.sep}b.spec.ts > b test [begin]
b > b${path.sep}b.spec.ts > b test [end]
a > a${path.sep}a.spec.ts > a test [begin]
a > a${path.sep}a.spec.ts > a test [end]`);
});
});
test('should order 1-3-1 projects', async ({ runGroups }, testInfo) => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
'e',
['d', 'c', 'b'],
'a',
]
});
const { exitCode, passed, timeline } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expectRunBefore(timeline, ['e'], ['d', 'c', 'b']);
expectRunBefore(timeline, ['d', 'c', 'b'], ['a']);
expect(passed).toBe(5);
});
test('should order 2-2-2 projects', async ({ runGroups }, testInfo) => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
['a', 'b'],
['d', 'c'],
['e', 'f'],
]
});
const { exitCode, passed, timeline } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expectRunBefore(timeline, ['a', 'b'], ['c', 'd']);
expectRunBefore(timeline, ['c', 'd'], ['e', 'f']);
expect(passed).toBe(6);
});
test('should run parallel groups sequentially without overlaps', async ({ runGroups }, testInfo) => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
['a', 'b', 'c', 'd'],
['a', 'b', 'c', 'd'],
['a', 'b', 'c', 'd'],
]
});
const { exitCode, passed, timeline } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
const expectedEndOfFirstPhase = events => {
const firstProjectEndIndex = project => events.findIndex(e => e.event === 'end' && e.titlePath[1] === project);
return Math.max(...['a', 'b', 'c', 'd'].map(firstProjectEndIndex));
};
const formatPhaseEvents = events => events.map(e => e.titlePath[1] + ':' + e.event);
let remainingTimeline = timeline;
for (let i = 0; i < 3; i++) {
const phaseEndIndex = expectedEndOfFirstPhase(remainingTimeline);
const firstPhase = formatPhaseEvents(remainingTimeline.slice(0, phaseEndIndex + 1));
firstPhase.sort();
expect(firstPhase, `check phase ${i}`).toEqual(['a:begin', 'a:end', 'b:begin', 'b:end', 'c:begin', 'c:end', 'd:begin', 'd:end']);
remainingTimeline = remainingTimeline.slice(phaseEndIndex + 1);
}
expect(remainingTimeline.length).toBe(0);
expect(passed).toBe(12);
});
test('should support phase with multiple project names', async ({ runGroups }, testInfo) => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
[
{ project: ['a', 'b', 'c'] }
],
[
{ project: ['d'] },
{ project: ['e', 'f'] }
],
]
});
const { exitCode, passed } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expect(passed).toBe(6);
});
test('should support varios syntax', async ({ runGroups }, testInfo) => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
'a',
['a', 'b'],
[
{ project: ['a', 'b'] }
],
[
{ project: ['a', 'b'] },
'c',
{ project: 'd' },
],
[{ project: 'e' }],
'f'
]
});
const { exitCode, passed } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expect(passed).toBe(11);
});
test('should support --group option', async ({ runGroups }, testInfo) => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
'a', 'b'
],
foo: [
['b', 'c']
],
bar: [
'd', 'e'
]
});
const formatPhaseEvents = events => events.map(e => e.titlePath[1] + ':' + e.event);
{
const { exitCode, passed, timeline } = await runGroups(configWithFiles, { group: 'default' });
expect(exitCode).toBe(0);
expect(passed).toBe(2);
expect(formatPhaseEvents(timeline)).toEqual(['a:begin', 'a:end', 'b:begin', 'b:end']);
}
{
const { exitCode, passed, timeline } = await runGroups(configWithFiles, { group: 'foo' });
expect(exitCode).toBe(0);
expect(passed).toBe(2);
const formatted = formatPhaseEvents(timeline);
formatted.sort();
expect(formatted).toEqual(['b:begin', 'b:end', 'c:begin', 'c:end']);
}
{
const { exitCode, passed, timeline } = await runGroups(configWithFiles, { group: 'bar' });
expect(exitCode).toBe(0);
expect(passed).toBe(2);
expect(formatPhaseEvents(timeline)).toEqual(['d:begin', 'd:end', 'e:begin', 'e:end']);
}
});
test('should throw when unknown --group is passed', async ({ runGroups }, testInfo) => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
'a', 'b'
],
foo: [
['b', 'c']
]
});
const { exitCode, output } = await runGroups(configWithFiles, { group: 'bar' });
expect(exitCode).toBe(1);
expect(output).toContain(`Cannot find project group 'bar' in the config`);
});
test('should support testMatch and testIgnore', async ({ runGroups }, testInfo) => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
[
{ project: ['a', 'b'], testMatch: ['**/a.spec.ts'] },
{ project: ['c', 'd'], testMatch: [/.*c.spec.ts/, '**/*d*'] },
{ project: ['e'], testIgnore: [/.*e.spec.ts/] },
{ project: ['f'], testMatch: /does not match/ },
],
]
});
const { exitCode, passed, timeline } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expect(passed).toBe(3);
expect(projectNames(timeline)).toEqual(['a', 'c', 'd']);
});
test('should support grep and grepInvert', async ({ runGroups }, testInfo) => {
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
[
{ project: ['a', 'b'], grep: /.*a test/ },
{ project: ['c', 'd'], grepInvert: [/.*c test/] },
{ project: ['e', 'f'], grep: /.*(e|f) test/, grepInvert: [/.*f test/] },
],
]
});
const { exitCode, passed, timeline } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expect(passed).toBe(3);
expect(projectNames(timeline)).toEqual(['a', 'd', 'e']);
});
test('should intercect gpoup and project level grep and grepInvert', async ({ runGroups }, testInfo) => {
const projectTemplates = {
'a': {
grep: /a test/,
grepInvert: [/no test/],
},
'b': {
grep: /.*b te.*/,
grepInvert: [/.*a test/],
},
'c': {
grepInvert: [/.*test/],
},
'd': {
grep: [/.*unkwnown test/],
},
};
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
[
{ project: ['a', 'b', 'c', 'd', 'e'], grep: /.*(b|c|d|e) test/, grepInvert: /.*d test/ },
],
]
}, projectTemplates);
const { exitCode, passed, timeline } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expect(passed).toBe(2);
expect(projectNames(timeline)).toEqual(['b', 'e']);
});
test('should intercect gpoup and project level testMatch and testIgnore', async ({ runGroups }, testInfo) => {
const projectTemplates = {
'a': {
testMatch: /.*a.spec.ts/,
testIgnore: [/no test/],
},
'b': {
testMatch: '**/b.spec.ts',
testIgnore: [/.*a.spec.ts/],
},
'c': {
testIgnore: [/.*no-match.spec.ts/],
},
'd': {
testMatch: [/.*unkwnown/],
},
};
const configWithFiles = createConfigWithProjects(['a', 'b', 'c', 'd', 'e', 'f'], testInfo, {
default: [
[
{ project: ['a', 'b', 'c', 'd'], testMatch: /.*(b|c|d).spec.ts/, testIgnore: /.*c.spec.ts/ },
{ project: ['c', 'd', 'e', 'f'], testIgnore: /.*[^ef].spec.ts/ },
],
]
}, projectTemplates);
const { exitCode, passed, timeline } = await runGroups(configWithFiles);
expect(exitCode).toBe(0);
expect(passed).toBe(3);
expect(projectNames(timeline)).toEqual(['b', 'e', 'f']);
});