Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/browser/existing-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export class ExistingBrowser extends Browser {
protected _calibration?: CalibrationResult;
protected _clientBridge?: ClientBridge;
protected _cdp: CDP | null = null;
protected _tags: Set<string> = new Set();

constructor(config: Config, opts: BrowserOpts) {
super(config, opts);
Expand Down Expand Up @@ -328,6 +329,14 @@ export class ExistingBrowser extends Browser {
protected _addMetaAccessCommands(session: WebdriverIO.Browser): void {
session.addCommand("setMeta", (key, value) => (this._meta[key] = value));
session.addCommand("getMeta", key => (key ? this._meta[key] : this._meta));

session.addCommand("addTag", (tag: string | string[]) => {
if (Array.isArray(tag)) {
tag.forEach(element => this._tags?.add(element));
} else {
this._tags?.add(tag);
}
});
}

protected _decorateUrlMethod(session: WebdriverIO.Browser): void {
Expand Down Expand Up @@ -584,6 +593,10 @@ export class ExistingBrowser extends Browser {
return this._meta;
}

get tags(): string[] {
return [...this._tags];
}

get cdp(): CDP | null {
return this._cdp;
}
Expand Down
4 changes: 3 additions & 1 deletion src/browser/history/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export interface PromiseRef<T = unknown> {
}

const shouldNotWrapCommand = (commandName: string): boolean =>
["addCommand", "overwriteCommand", "extendOptions", "setMeta", "getMeta", "runStep"].includes(commandName);
["addCommand", "overwriteCommand", "extendOptions", "addTag", "setMeta", "getMeta", "runStep"].includes(
commandName,
);

export const shouldPropagateFn = (parentNode: TestStep, currentNode: TestStep): boolean =>
isGroup(parentNode) || isGroup(currentNode);
Expand Down
2 changes: 2 additions & 0 deletions src/browser/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ declare global {

setMeta(this: WebdriverIO.Browser, key: string, value: unknown): Promise<void>;

addTag(this: WebdriverIO.Browser, tag: string | string[]): Promise<void>;

extendOptions(this: WebdriverIO.Browser, opts: { [name: string]: unknown }): Promise<void>;

getConfig(this: WebdriverIO.Browser): Promise<BrowserConfig>;
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/list-tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const registerCmd = (cliTool: ListTestsCmd, testplane: Testplane): void =
.option("--formatter [name]", "return tests in specified format", String, Formatters.LIST)
.arguments("[paths...]")
.action(async (paths: string[], options: ListTestsCmdOpts) => {
const { grep, browser: browsers, set: sets } = cliTool;
const { grep, tag, browser: browsers, set: sets } = cliTool;
const { ignore, silent, outputFile, formatter } = options;

try {
Expand All @@ -40,6 +40,7 @@ export const registerCmd = (cliTool: ListTestsCmd, testplane: Testplane): void =
browsers,
sets,
grep,
tag,
ignore,
silent,
runnableOpts: {
Expand Down
2 changes: 2 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const run = async (opts: TestplaneRunOpts = {}): Promise<void> => {
browser: browsers,
set: sets,
grep,
tag,
updateRefs,
inspect,
inspectBrk,
Expand All @@ -104,6 +105,7 @@ export const run = async (opts: TestplaneRunOpts = {}): Promise<void> => {
browsers,
sets,
grep,
tag,
updateRefs,
requireModules,
inspectMode: (inspect || inspectBrk) && { inspect, inspectBrk },
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type {
TestFunctionCtx,
ExecutionThreadCtx,
Cookie,
TestTag,
} from "./types";
export type { Config } from "./config";
export { TimeTravelMode } from "./config";
Expand Down
6 changes: 5 additions & 1 deletion src/runner/test-runner/regular-test-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ module.exports = class RegularTestRunner extends RunnableEmitter {
});
}

_applyTestResults({ meta, testplaneCtx = {}, history = [] }) {
_applyTestResults({ tags, meta, testplaneCtx = {}, history = [] }) {
testplaneCtx.assertViewResults = AssertViewResults.fromRawObject(testplaneCtx.assertViewResults || []);
this._test.assertViewResults = testplaneCtx.assertViewResults.get();

Expand All @@ -80,6 +80,10 @@ module.exports = class RegularTestRunner extends RunnableEmitter {
this._test.hermioneCtx = testplaneCtx;
this._test.history = history;

if (tags) {
tags.forEach(tag => this._test.addTag(tag));
}

this._test.duration = Date.now() - this._test.startTime;
}

Expand Down
4 changes: 2 additions & 2 deletions src/test-reader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class TestReader extends EventEmitter {
}

async read(options: TestReaderOpts): Promise<Record<string, Test[]>> {
const { paths, browsers, ignore, sets, grep, runnableOpts } = options;
const { paths, browsers, ignore, sets, grep, tag, runnableOpts } = options;

const { fileExtensions } = this.#config.system;
const envSets = env.parseCommaSeparatedValue(["TESTPLANE_SETS", "HERMIONE_SETS"]).value;
Expand All @@ -46,7 +46,7 @@ export class TestReader extends EventEmitter {

const filesByBro = setCollection.groupByBrowser();
const testsByBro = _.mapValues(filesByBro, (files, browserId) =>
parser.parse(files, { browserId, config: this.#config.forBrowser(browserId), grep }),
parser.parse(files, { browserId, config: this.#config.forBrowser(browserId), grep, tag }),
);

validateTests(testsByBro, options, this.#config);
Expand Down
38 changes: 38 additions & 0 deletions src/test-reader/mocha-reader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,46 @@ const { MasterEvents } = require("../../events");
const { getMethodsByInterface } = require("./utils");
const { enableSourceMaps } = require("../../utils/typescript");

function getTagParser(original) {
return function (title, paramsOrFn, fn) {
if (typeof paramsOrFn === "function") {
return original.call(this, title, paramsOrFn);
} else {
const test = original.call(this, title, fn);

if (paramsOrFn?.tag) {
if (Array.isArray(paramsOrFn.tag)) {
test.tags = paramsOrFn.tag.map(title => ({ title, dynamic: false }));
} else {
test.tags = [{ title: paramsOrFn.tag, dynamic: false }];
}
}

return test;
}
};
}

async function readFiles(files, { esmDecorator, config, eventBus, runnableOpts }) {
const mocha = new Mocha(config);

mocha.suite.on("pre-require", context => {
const originalDescribe = context.describe;
const originalIt = context.it;

context.describe = getTagParser(originalDescribe);
context.context = getTagParser(originalDescribe);

context.describe.only = originalDescribe.only;
context.describe.skip = originalDescribe.skip;

context.it = getTagParser(originalIt);
context.specify = getTagParser(originalIt);

context.it.only = originalIt.only;
context.it.skip = originalIt.skip;
});

mocha.fullTrace();

initBuildContext(eventBus);
Expand Down
8 changes: 4 additions & 4 deletions src/test-reader/mocha-reader/tree-builder-decorator.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ class TreeBuilderDecorator {
}

addSuite(mochaSuite) {
const { id: mochaId } = mochaSuite;
const { id: mochaId, tags } = mochaSuite;
const file = computeFile(mochaSuite) ?? "unknown-file";

const positionInFile = this.#suiteCounter.get(file) || 0;
const id = mochaSuite.root ? mochaId : crypto.getShortMD5(file) + positionInFile;
const suite = this.#mkTestObject(Suite, mochaSuite, { id });
const suite = this.#mkTestObject(Suite, mochaSuite, { id, tags });

this.#applyConfig(suite, mochaSuite);
this.#treeBuilder.addSuite(suite, this.#getParent(mochaSuite, null));
Expand All @@ -34,9 +34,9 @@ class TreeBuilderDecorator {
}

addTest(mochaTest) {
const { fn } = mochaTest;
const { fn, tags } = mochaTest;
const id = crypto.getShortMD5(mochaTest.fullTitle());
const test = this.#mkTestObject(Test, mochaTest, { id, fn });
const test = this.#mkTestObject(Test, mochaTest, { id, fn, tags });

this.#applyConfig(test, mochaTest);
this.#treeBuilder.addTest(test, this.#getParent(mochaTest));
Expand Down
27 changes: 24 additions & 3 deletions src/test-reader/test-object/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,51 @@ import _ from "lodash";
import { ConfigurableTestObject } from "./configurable-test-object";
import { Hook } from "./hook";
import { Test } from "./test";
import type { TestObjectData, ConfigurableTestObjectData, TestFunction, TestFunctionCtx } from "./types";
import type { TestObjectData, ConfigurableTestObjectData, TestFunction, TestFunctionCtx, TestTag } from "./types";

type SuiteOpts = Pick<ConfigurableTestObjectData, "file" | "id" | "location"> & TestObjectData;
type SuiteOpts = Pick<ConfigurableTestObjectData, "file" | "id" | "location"> & TestObjectData & { tags: TestTag[] };

export class Suite extends ConfigurableTestObject {
#suites: this[];
#tests: Test[];
#beforeEachHooks: Hook[];
#afterEachHooks: Hook[];
public tags: Map<string, boolean>;

static create<T extends Suite>(this: new (opts: SuiteOpts) => T, opts: SuiteOpts): T {
return new this(opts);
}

// used inside test
constructor({ title, file, id, location }: SuiteOpts = {} as SuiteOpts) {
constructor({ title, file, id, location, tags }: SuiteOpts = {} as SuiteOpts) {
super({ title, file, id, location });

this.tags = new Map(tags?.map(({ title, dynamic }) => [title, Boolean(dynamic)]) || []);
this.#suites = [];
this.#tests = [];
this.#beforeEachHooks = [];
this.#afterEachHooks = [];
}

addTag(tag: string | string[], dynamic = false): void {
if (Array.isArray(tag)) {
tag.forEach(element => this.tags.set(element, dynamic));
} else {
this.tags.set(tag, dynamic);
}
}

hasTag(tag: string): boolean {
return this.tags.has(tag);
}

getTags(): TestTag[] {
return Array.from(this.tags.keys()).map(title => ({
title,
dynamic: this.tags.get(title),
}));
}

addSuite(suite: Suite): this {
return this.#addChild(suite, this.#suites);
}
Expand Down
27 changes: 25 additions & 2 deletions src/test-reader/test-object/test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,51 @@
import { ConfigurableTestObject } from "./configurable-test-object";
import type { TestObjectData, TestFunction, TestFunctionCtx } from "./types";
import type { TestObjectData, TestFunction, TestFunctionCtx, TestTag } from "./types";

type TestOpts = TestObjectData &
Pick<ConfigurableTestObject, "file" | "id" | "location"> & {
fn: TestFunction<TestFunctionCtx>;
tags?: TestTag[];
};

export class Test extends ConfigurableTestObject {
public fn: TestFunction<TestFunctionCtx>;
public tags: Map<string, boolean>;
public err?: Error;

static create<T extends Test>(this: new (opts: TestOpts) => T, opts: TestOpts): T {
return new this(opts);
}

constructor({ title, file, id, location, fn }: TestOpts) {
constructor({ title, file, id, location, fn, tags }: TestOpts) {
super({ title, file, id, location });

this.fn = fn;
this.tags = new Map(tags?.map(({ title, dynamic }) => [title, Boolean(dynamic)]) || []);
}

addTag(tag: string | string[]): void {
if (Array.isArray(tag)) {
tag.forEach(element => this.tags.set(element, true));
} else {
this.tags.set(tag, true);
}
}

hasTag(tag: string): boolean {
return this.tags.has(tag);
}

getTags(): TestTag[] {
return Array.from(this.tags.keys()).map(title => ({
title,
dynamic: this.tags.get(title),
}));
}

clone(): Test {
return new Test({
title: this.title,
tags: this.getTags(),
file: this.file,
id: this.id,
location: this.location,
Expand Down
16 changes: 16 additions & 0 deletions src/test-reader/test-object/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,17 @@ export interface TestHookDefinition {
<T extends Partial<TestFunctionCtx> = TestFunctionCtx>(fn?: TestFunction<T>): Hook;
}

export type DefinitionParams = {
tag?: string | string[];
};

export interface TestDefinition {
<T extends Partial<TestFunctionCtx> = TestFunctionCtx>(title: string, fn?: TestFunction<T>): Test;
<T extends Partial<TestFunctionCtx> = TestFunctionCtx>(
title: string,
params: DefinitionParams,
fn?: TestFunction<T>,
): Test;

only: <T extends Partial<TestFunctionCtx> = TestFunctionCtx>(title: string, fn?: TestFunction<T>) => Test;

Expand All @@ -45,7 +54,14 @@ export interface TestDefinition {

export interface SuiteDefinition {
(title: string, fn: (this: Suite) => void): Suite;
(title: string, params: DefinitionParams, fn: (this: Suite) => void): Suite;

only: (title: string, fn: (this: Suite) => void) => Suite;

skip: (title: string, fn: (this: Suite) => void) => Suite;
}

export type TestTag = {
title: string;
dynamic?: boolean;
};
22 changes: 20 additions & 2 deletions src/test-reader/test-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ import path from "path";
import fs from "fs-extra";
import * as logger from "../utils/logger";
import { getShortMD5 } from "../utils/crypto";
import { Test } from "./test-object";
import { Suite, Test } from "./test-object";
import { Config } from "../config";
import { BrowserConfig } from "../config/browser-config";
import type { ReadTestsOpts } from "../testplane";
import { TagFilter } from "../utils/cli";

export type TestParserParseOpts = {
browserId: string;
grep?: RegExp;
tag?: TagFilter;
config: BrowserConfig;
};

Expand Down Expand Up @@ -135,7 +137,7 @@ export class TestParser extends EventEmitter {
});
}

parse(files: string[], { browserId, config, grep }: TestParserParseOpts): Test[] {
parse(files: string[], { browserId, config, grep, tag }: TestParserParseOpts): Test[] {
const treeBuilder = new TreeBuilder();

this.#buildInstructions.exec(files, { treeBuilder, browserId, config });
Expand All @@ -144,6 +146,22 @@ export class TestParser extends EventEmitter {
treeBuilder.addTestFilter((test: Test) => grep.test(test.fullTitle()));
}

if (tag) {
treeBuilder.addTestFilter((test: Test) => {
let current: Test | Suite | null = test;

while (current) {
if (tag(current.tags)) {
return true;
} else {
current = current.parent;
}
}

return false;
});
}

if (config.lastFailed?.only) {
if (!this.#failedTests.size) {
return [];
Expand Down
Loading
Loading