Skip to content

Commit 7c4595c

Browse files
author
NikolayAvramov
committed
Merge branch 'add-test-filtering' of https://github.com/AutomateThePlanet/BELLATRIX-JavaScript into add-web-tests
2 parents 80596ce + 7e2b4a7 commit 7c4595c

File tree

18 files changed

+9951
-7496
lines changed

18 files changed

+9951
-7496
lines changed

.vscode/launch.json

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,49 @@
1010
"run",
1111
"test"
1212
],
13+
"cwd": "${workspaceFolder}/example",
14+
"console": "integratedTerminal",
15+
"internalConsoleOptions": "neverOpen",
16+
"noDebug": true, // ignore VSCode warning
17+
},
18+
{
19+
"type": "node",
20+
"request": "launch",
21+
"name": "Debug main process",
22+
"runtimeExecutable": "npm",
23+
"runtimeArgs": [
24+
"run",
25+
"test"
26+
],
1327
"skipFiles": [
1428
"<node_internals>/**"
1529
],
1630
"cwd": "${workspaceFolder}/example",
1731
"console": "integratedTerminal",
18-
"internalConsoleOptions": "neverOpen"
32+
"internalConsoleOptions": "neverOpen",
33+
"presentation": {
34+
"hidden": true
35+
},
36+
},
37+
{
38+
"type": "node",
39+
"request": "attach",
40+
"name": "Debug child process",
41+
"port": 12016,
42+
"skipFiles": [
43+
"<node_internals>/**"
44+
],
45+
"timeout": 5000,
46+
"restart": false,
47+
"presentation": {
48+
"hidden": true
49+
},
50+
}
51+
],
52+
"compounds": [
53+
{
54+
"name": "Debug Tests",
55+
"configurations": ["Debug main process", "Debug child process"]
1956
}
2057
]
21-
}
58+
}

@bellatrix/core/package-lock.json

Lines changed: 936 additions & 951 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

@bellatrix/core/package.build.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"USE THIS REGEX TO SELECT THE EXPORTS, THEN CHANGE THEM WITH JS OR STH": "YOU ARE AN IDIOT, JUST IMPORT, MODIFY THE JS OBJECT AND STRINGIFY",
2+
"USE THIS REGEX TO SELECT THE EXPORTS, THEN CHANGE THEM WITH JS OR STH": "JUST IMPORT, MODIFY THE JS OBJECT AND STRINGIFY",
33
"######": ".*\"exports\": (?=\\{((?:[^{}]++)++)\\})",
44
"name": "@bellatrix/core",
55
"version": "0.0.1",

@bellatrix/core/src/infrastructure/PluginExecutionEngine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { TestMetadata, getCurrentTest, getSuiteMetadata, getTestMetadata } from '@bellatrix/core/test/props';
1+
import { getCurrentTest, getSuiteMetadata, getTestMetadata } from '@bellatrix/core/test/props';
22
import { ServiceLocator } from '@bellatrix/core/utilities';
33
import { BellatrixTest, Plugin } from '.';
44

@bellatrix/core/src/test/playwright.ts

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import inspector from 'inspector';
2+
3+
let isDebuggerAttached = false;
4+
if (inspector.url() !== undefined && new URL(inspector.url()!).port === '12016' && !isDebuggerAttached) {
5+
inspector.waitForDebugger();
6+
isDebuggerAttached = true;
7+
}
8+
19
import * as nativeLibrary from '@playwright/test';
210
import 'reflect-metadata';
311

@@ -11,6 +19,7 @@ import type { ConfigureFn, Method, MethodNames, ParameterlessCtor, TestFn } from
1119

1220
const BaseTest = ServiceLocator.resolveType(BellatrixTest);
1321
const testSettings = BellatrixSettings.get().frameworkSettings.testSettings;
22+
const testFilters = JSON.parse(process.env.BELLATRIX_TEST_FILTER!);
1423

1524
function getSymbolMethods<T extends BellatrixTest>(type: ParameterlessCtor<T>) {
1625
return {
@@ -33,6 +42,66 @@ export function SuiteDecorator<T extends BellatrixTest>(target: ParameterlessCto
3342
const testMethods = Object.getOwnPropertyNames(testClass).filter(method => typeof testClass[method] === 'function' && Reflect.hasMetadata(Symbols.TEST, testClass[method]));
3443
const title = target.name; // or passed as @Suite('title') or similar
3544

45+
const tests: Map<string, unknown> = new Map;
46+
for (const testMethod of testMethods) {
47+
const testMetadata = getTestMetadata(testClass[testMethod], testClass);
48+
49+
for (const [filterKey, filterValue] of Object.entries(testFilters)) {
50+
if (filterKey == 'suiteName') {
51+
if (Array.isArray(filterValue)) {
52+
throw new Error('no more than one --suiteName argument allowed as it equals to AND operator, use regex');
53+
}
54+
55+
if (!(new RegExp(String(filterValue), 'i').test(testMetadata[filterKey]))) {
56+
testMetadata.shouldSkip = true;
57+
break;
58+
}
59+
}
60+
61+
if (filterKey == 'testName') {
62+
if (Array.isArray(filterValue)) {
63+
throw new Error('no more than one --testName argument allowed as it equals to AND operator, use regex');
64+
}
65+
66+
if (!(new RegExp(String(filterValue), 'i').test(testMetadata[filterKey]))) {
67+
testMetadata.shouldSkip = true;
68+
break;
69+
}
70+
} else {
71+
if (Array.isArray(filterValue)) {
72+
let remainingMatches = filterValue.length;
73+
filterValue.forEach(singleFilterValue => {
74+
if (new RegExp(String(singleFilterValue), 'i').test(String(testMetadata.customData.get(filterKey)))) {
75+
remainingMatches--;
76+
}
77+
});
78+
79+
if (remainingMatches > 0) {
80+
testMetadata.shouldSkip = true;
81+
break;
82+
}
83+
} else if (!(new RegExp(String(filterValue), 'i').test(String(testMetadata.customData.get(filterKey))))) {
84+
testMetadata.shouldSkip = true;
85+
break;
86+
}
87+
}
88+
}
89+
90+
const currentTest = async () => {
91+
try {
92+
await testClass[testMethod].call(testClassInstance);
93+
} catch (error) {
94+
if (error instanceof Error) {
95+
testMetadata.error = error;
96+
throw error;
97+
}
98+
}
99+
};
100+
101+
Object.defineProperty(currentTest, 'name', { value: testMethod }); // !!! Important
102+
tests.set(testMethod, currentTest);
103+
}
104+
36105
nativeLibrary.test.describe(title, () => {
37106
nativeLibrary.test.beforeAll(async () => await testClassSymbolMethods.beforeAll.call(testClassInstance));
38107

@@ -49,43 +118,41 @@ export function SuiteDecorator<T extends BellatrixTest>(target: ParameterlessCto
49118

50119
nativeLibrary.test.afterAll(async () => await testClassSymbolMethods.afterAll.call(testClassInstance));
51120

52-
for (const testMethod of testMethods) {
53-
nativeLibrary.test(testMethod, async () => {
54-
nativeLibrary.test.setTimeout(testSettings.testTimeout!);
55-
try {
56-
await testClass[testMethod].call(testClassInstance);
57-
} catch (error) {
58-
if (error instanceof Error) {
59-
getTestMetadata(testClass[testMethod], testClass).error = error;
60-
throw error;
61-
}
62-
}
63-
});
64-
}
121+
tests.forEach((testFunction, testName) => {
122+
const testMetadata = getTestMetadata(testClass[(testFunction as Function).name], testClass);
123+
if (testMetadata.shouldSkip) {
124+
nativeLibrary.test.skip(testName, testFunction as never);
125+
} else if (testMetadata.only) {
126+
nativeLibrary.test.only(testName, testFunction as never);
127+
} else {
128+
nativeLibrary.test(testName, testFunction as never);
129+
}
130+
});
65131
});
66132
}
67133

68134
function test<T extends BellatrixTest, K extends string>(target: T, key: K extends MethodNames<BellatrixTest> ? never : K): void;
69135
function test(name: string, fn: TestFn<TestProps>): void;
70-
function test<T extends BellatrixTest, K extends string>(name: unknown, fn: unknown): void {
71-
if (name instanceof BellatrixTest) {
72-
const target = name as T;
136+
function test<T extends BellatrixTest, K extends string>(nameOrTarget: T | string, fn: unknown): void {
137+
if (nameOrTarget instanceof BellatrixTest) {
138+
const target = nameOrTarget as T;
73139
const key = fn as K extends MethodNames<BellatrixTest> ? never : K;
74140
defineTestMetadata(target[key as keyof T] as (...args: unknown[]) => (Promise<void> | void), target.constructor as ParameterlessCtor<T>);
75141
return;
76142
}
143+
77144
if (!currentTestClass) {
78145
throw Error('test cannot be called outside of describe block.');
79146
}
80147

81148
if (globalConfigureBlock) {
82-
currentTestClass.constructor.prototype.configure = globalConfigureBlock;
149+
// currentTestClass.constructor.prototype.configure = globalConfigureBlock;
83150
}
84151

85152
const testFn = async () => await (fn as TestFn<TestProps>)(ServiceLocator.resolve(TestProps));
86-
Object.defineProperty(testFn, 'name', { value: name });
87-
currentTestClass.constructor.prototype[name as keyof T] = testFn;
88-
test(currentTestClass, name as string);
153+
Object.defineProperty(testFn, 'name', { value: nameOrTarget });
154+
// currentTestClass.constructor.prototype[nameOrTarget as keyof T] = testFn;
155+
// test(currentTestClass, nameOrTarget as string);
89156
}
90157

91158
function describe(title: string, fn: () => void): void {

@bellatrix/core/src/test/vitest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ function test<T extends BellatrixTest, K extends string>(nameOrTarget: unknown,
134134
defineTestMetadata(target[key as keyof T] as (...args: unknown[]) => (Promise<void> | void), target.constructor as ParameterlessCtor<T>);
135135
return;
136136
}
137+
137138
if (!currentTestClass) {
138139
throw Error('test cannot be called outside of describe block.');
139140
}

@bellatrix/extras/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

@bellatrix/extras/src/hooks/ExtraWebHooks.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,31 @@ export class ExtraWebHooks {
66
static addComponentBDDLogging(): void {
77
const locale = Intl.DateTimeFormat().resolvedOptions().locale; // TODO: add locale option in the config
88

9-
WebComponentHooks.addListenerTo(Anchor).before('click', (anchor) => console.log(`clicking ${anchor.componentName}`));
10-
WebComponentHooks.addListenerTo(Button).before('click', (button) => console.log(`clicking ${button.componentName}`));
11-
WebComponentHooks.addListenerTo(ColorInput).before('setColor', (colorInput, color) => console.log(`setting '${color}' into ${colorInput.componentName}`));
12-
WebComponentHooks.addListenerTo(CheckBox).before('check', (checkBox) => console.log(`checking ${checkBox.componentName}`));
13-
WebComponentHooks.addListenerTo(CheckBox).before('uncheck', (checkBox) => console.log(`unchecking ${checkBox.componentName}`));
14-
WebComponentHooks.addListenerTo(DateInput).before('setDate', (dateInput, date) => console.log(`setting ${dateInput.componentName} to ${date.toLocaleDateString(locale)}`));
15-
WebComponentHooks.addListenerTo(DateTimeInput).before('setTime', (dateTimeInput, dateTime) => console.log(`setting ${dateTimeInput.componentName} to ${dateTime.toLocaleString()}`));
16-
WebComponentHooks.addListenerTo(EmailField).before('setEmail', (emailField, email) => console.log(`typing '${email}' into ${emailField.componentName}`));
17-
WebComponentHooks.addListenerTo(FileInput).before('upload', (fileInput, filePath) => console.log(`uploading '${filePath}' into ${fileInput.componentName}`));
18-
WebComponentHooks.addListenerTo(MonthInput).before('setMonth', (monthInput, year, month) => console.log(`setting ${monthInput} to ${new Date(year, month - 1).toLocaleDateString(locale, { month: 'long', year: 'numeric' })}`));
19-
WebComponentHooks.addListenerTo(NumberInput).before('setNumber', (numberInput, number) => console.log(`setting ${numberInput.componentName} to ${number}`));
20-
WebComponentHooks.addListenerTo(PasswordField).before('setPassword', (passwordField, _) => console.log(`typing '********' into ${passwordField.componentName}`));
21-
WebComponentHooks.addListenerTo(PhoneField).before('setPhone', (phoneField, phone) => console.log(`typing '${phone}' into ${phoneField.componentName}`));
22-
WebComponentHooks.addListenerTo(RangeInput).before('setValue', (rangeInput, value) => console.log(`setting ${rangeInput.componentName} to ${value}`));
23-
WebComponentHooks.addListenerTo(SearchField).before('setSearch', (searchField, search) => console.log(`typing '${search}' into ${searchField.componentName}`));
24-
WebComponentHooks.addListenerTo(Select).before('selectByText', (select, text) => console.log(`selecting '${text}' from ${select.componentName}`));
25-
WebComponentHooks.addListenerTo(Select).before('selectByIndex', (select, index) => console.log(`selecting index ${index} from ${select.componentName}`));
26-
WebComponentHooks.addListenerTo(Select).before('selectByValue', (select, value) => console.log(`selecting value="${value}" from ${select.componentName}`));
27-
WebComponentHooks.addListenerTo(TextArea).before('setText', (textArea, text) => console.log(`typing '${text}' into ${textArea.componentName}`));
28-
WebComponentHooks.addListenerTo(TextField).before('setText', (textField, text) => console.log(`typing '${text}' into ${textField.componentName}`));
29-
WebComponentHooks.addListenerTo(TimeInput).before('setTime', (timeInput, hours, minutes, seconds) => console.log(`setting ${timeInput.componentName} to ${[hours, minutes, seconds].map(n => String(n ?? 0).padStart(2, '0')).join(':')}`));
30-
WebComponentHooks.addListenerTo(UrlField).before('setUrl', (urlField, url) => console.log(`typing '${url}' into ${urlField.componentName}`));
31-
WebComponentHooks.addListenerTo(WeekInput).before('setWeek', (weekInput, year, weekNumber) => console.log(`setting ${weekInput.componentName} to ${year}-W${weekNumber.toString().padStart(2, '0')}`));
32-
WebComponentHooks.addListenerTo(WebComponent).before('scrollIntoView', (component) => console.log(`scrolling ${component} into view`));
33-
WebComponentHooks.addListenerTo(WebComponent).before('hover', (component) => console.log(`hovering ${component}`));
34-
WebComponentHooks.addListenerTo(WebComponent).before('focus', (component) => console.log(`focusing ${component}`));
9+
WebComponentHooks.addListenerTo(Anchor).before('click', function() { console.log(`clicking ${this.componentName}`); });
10+
WebComponentHooks.addListenerTo(Button).before('click', function() { console.log(`clicking ${this.componentName}`); });
11+
WebComponentHooks.addListenerTo(ColorInput).before('setColor', function(color) { console.log(`setting '${color}' into ${this.componentName}`); });
12+
WebComponentHooks.addListenerTo(CheckBox).before('check', function() { console.log(`checking ${this.componentName}`); });
13+
WebComponentHooks.addListenerTo(CheckBox).before('uncheck', function() { console.log(`unchecking ${this.componentName}`); });
14+
WebComponentHooks.addListenerTo(DateInput).before('setDate', function(date) { console.log(`setting ${this.componentName} to ${date.toLocaleDateString(locale)}`); });
15+
WebComponentHooks.addListenerTo(DateTimeInput).before('setTime', function(dateTime) { console.log(`setting ${this.componentName} to ${dateTime.toLocaleString()}`); });
16+
WebComponentHooks.addListenerTo(EmailField).before('setEmail', function(email) { console.log(`typing '${email}' into ${this.componentName}`); });
17+
WebComponentHooks.addListenerTo(FileInput).before('upload', function(filePath) { console.log(`uploading '${filePath}' into ${this.componentName}`); });
18+
WebComponentHooks.addListenerTo(MonthInput).before('setMonth', function(year, month) { console.log(`setting ${this.componentName} to ${new Date(year, month - 1).toLocaleDateString(locale, { month: 'long', year: 'numeric' })}`); });
19+
WebComponentHooks.addListenerTo(NumberInput).before('setNumber', function(number) { console.log(`setting ${this.componentName} to ${number}`); });
20+
WebComponentHooks.addListenerTo(PasswordField).before('setPassword', function() { console.log(`typing '********' into ${this.componentName}`); });
21+
WebComponentHooks.addListenerTo(PhoneField).before('setPhone', function(phone) { console.log(`typing '${phone}' into ${this.componentName}`); });
22+
WebComponentHooks.addListenerTo(RangeInput).before('setValue', function(value) { console.log(`setting ${this.componentName} to ${value}`); });
23+
WebComponentHooks.addListenerTo(SearchField).before('setSearch', function(search) { console.log(`typing '${search}' into ${this.componentName}`); });
24+
WebComponentHooks.addListenerTo(Select).before('selectByText', function(text) { console.log(`selecting '${text}' from ${this.componentName}`); });
25+
WebComponentHooks.addListenerTo(Select).before('selectByIndex', function(index) { console.log(`selecting index ${index} from ${this.componentName}`); });
26+
WebComponentHooks.addListenerTo(Select).before('selectByValue', function(value) { console.log(`selecting value="${value}" from ${this.componentName}`); });
27+
WebComponentHooks.addListenerTo(TextArea).before('setText', function(text) { console.log(`typing '${text}' into ${this.componentName}`); });
28+
WebComponentHooks.addListenerTo(TextField).before('setText', function(text) { console.log(`typing '${text}' into ${this.componentName}`); });
29+
WebComponentHooks.addListenerTo(TimeInput).before('setTime', function(hours, minutes, seconds) { console.log(`setting ${this.componentName} to ${[hours, minutes, seconds].map(n => String(n ?? 0).padStart(2, '0')).join(':')}`); });
30+
WebComponentHooks.addListenerTo(UrlField).before('setUrl', function(url) { console.log(`typing '${url}' into ${this.componentName}`); });
31+
WebComponentHooks.addListenerTo(WeekInput).before('setWeek', function(year, weekNumber) { console.log(`setting ${this.componentName} to ${year}-W${weekNumber.toString().padStart(2, '0')}`); });
32+
WebComponentHooks.addListenerTo(WebComponent).before('scrollIntoView', function() { console.log(`scrolling ${this.componentName} into view`); });
33+
WebComponentHooks.addListenerTo(WebComponent).before('hover', function() { console.log(`hovering ${this.componentName}`); });
34+
WebComponentHooks.addListenerTo(WebComponent).before('focus', function() { console.log(`focusing ${this.componentName}`); });
3535
}
3636
}

@bellatrix/runner/bellatrix.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ if (parseInt(nodeVersion[0].replace()) < 20 || (parseInt(nodeVersion[0]) == 20 &
77
throw Error(`You need Node runtime version 20.9.0 minimum. Current version: ${process.versions.node}`);
88
}
99

10-
import { spawnSync } from 'child_process';
10+
import { spawnSync, fork } from 'child_process';
1111
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
1212
import { join, dirname, isAbsolute, relative } from 'path';
1313
import { pathToFileURL } from 'url';
@@ -192,7 +192,7 @@ switch (config.frameworkSettings.testSettings.testFramework) {
192192
const tsPathsEsmLoaderPath = new URL(import.meta.resolve('ts-paths-esm-loader')).pathname;
193193
const cliPath = findFilePath([ 'node_modules/playwright/cli.js' ]);
194194

195-
const cliArgs = [ cliPath, 'test' ];
195+
const cliArgs = [ 'test', testsDirectory ];
196196

197197
switch (reporter) {
198198
case 'json': {
@@ -217,12 +217,18 @@ switch (config.frameworkSettings.testSettings.testFramework) {
217217

218218
// cliArgs.push('--ui'); // TODO: make it an option
219219

220-
spawnSync('node', cliArgs, {
220+
const child = fork(cliPath, cliArgs, {
221221
stdio: 'inherit',
222222
env: {
223223
...process.env,
224224
NODE_OPTIONS: `--loader=${tsPathsEsmLoaderPath} --experimental-specifier-resolution=node --no-warnings`,
225-
}
225+
},
226+
execArgv: ['--inspect=12016'],
227+
});
228+
229+
// Handle child process events (optional)
230+
child.on('exit', (code) => {
231+
console.log(`Child process exited with code ${code}`);
226232
});
227233

228234
break;
@@ -264,7 +270,7 @@ switch (config.frameworkSettings.testSettings.testFramework) {
264270
env: {
265271
...process.env,
266272
NODE_OPTIONS: `--loader=${tsPathsEsmLoaderPath} --experimental-specifier-resolution=node --no-warnings`,
267-
}
273+
},
268274
});
269275

270276
break;

0 commit comments

Comments
 (0)