@thinkwise/testwise 0.1.12 → 0.1.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Testwise.ts +5 -1
- package/components/filter/FilterForm.ts +1 -1
- package/components/filter/FindForm.ts +1 -1
- package/components/grid/Grid.ts +88 -12
- package/components/grid/GridObjects.ts +16 -8
- package/components/index.ts +7 -3
- package/components/pop-up/PopUpComponent.ts +1 -1
- package/components/tab/BaseTab.ts +42 -0
- package/components/tab/BaseTabObjects.ts +23 -0
- package/components/tab/ComponentTab.ts +35 -0
- package/components/tab/ComponentTabObjects.ts +14 -0
- package/components/tab/DetailTab.ts +20 -0
- package/components/tab/DetailTabObjects.ts +10 -0
- package/components/task/TaskBar.ts +29 -9
- package/components/task/TaskBarObjects.ts +24 -0
- package/components/task/TaskTile.ts +28 -0
- package/components/task/TaskTileObjects.ts +20 -0
- package/controls/LookupDropdown.ts +54 -0
- package/controls/index.ts +1 -0
- package/dist/Testwise.js +4 -2
- package/dist/Testwise.js.map +1 -1
- package/dist/biome.json +52 -0
- package/dist/components/filter/FilterForm.d.ts +5 -0
- package/dist/components/filter/FilterForm.js +10 -0
- package/dist/components/filter/FilterForm.js.map +1 -0
- package/dist/components/filter/FindForm.d.ts +5 -0
- package/dist/components/filter/FindForm.js +10 -0
- package/dist/components/filter/FindForm.js.map +1 -0
- package/dist/components/grid/Grid.d.ts +25 -6
- package/dist/components/grid/Grid.js +80 -16
- package/dist/components/grid/Grid.js.map +1 -1
- package/dist/components/grid/GridObjects.d.ts +5 -3
- package/dist/components/grid/GridObjects.js +9 -6
- package/dist/components/grid/GridObjects.js.map +1 -1
- package/dist/components/index.d.ts +7 -3
- package/dist/components/index.js +7 -3
- package/dist/components/index.js.map +1 -1
- package/dist/components/pop-up/PopUpComponent.js +1 -1
- package/dist/components/pop-up/PopUpComponent.js.map +1 -1
- package/dist/components/pop-up/{PopUpComponentModels.js → PopUpComponentObjects.js} +1 -1
- package/dist/components/pop-up/PopUpComponentObjects.js.map +1 -0
- package/dist/components/tab/BaseTab.d.ts +11 -0
- package/dist/components/tab/BaseTab.js +34 -0
- package/dist/components/tab/BaseTab.js.map +1 -0
- package/dist/components/tab/BaseTabObjects.d.ts +10 -0
- package/dist/components/tab/BaseTabObjects.js +14 -0
- package/dist/components/tab/BaseTabObjects.js.map +1 -0
- package/dist/components/tab/ComponentTab.d.ts +11 -0
- package/dist/components/tab/ComponentTab.js +27 -0
- package/dist/components/tab/ComponentTab.js.map +1 -0
- package/dist/components/tab/ComponentTabObjects.d.ts +7 -0
- package/dist/components/tab/ComponentTabObjects.js +10 -0
- package/dist/components/tab/ComponentTabObjects.js.map +1 -0
- package/dist/components/tab/DetailTab.d.ts +9 -0
- package/dist/components/tab/DetailTab.js +15 -0
- package/dist/components/tab/DetailTab.js.map +1 -0
- package/dist/components/tab/DetailTabObjects.d.ts +5 -0
- package/dist/components/tab/DetailTabObjects.js +7 -0
- package/dist/components/tab/DetailTabObjects.js.map +1 -0
- package/dist/components/task/TaskBar.d.ts +17 -0
- package/dist/components/task/TaskBar.js +23 -0
- package/dist/components/task/TaskBar.js.map +1 -0
- package/dist/components/task/TaskBarObjects.d.ts +17 -0
- package/dist/components/task/TaskBarObjects.js +19 -0
- package/dist/components/task/TaskBarObjects.js.map +1 -0
- package/dist/components/task/TaskTile.d.ts +14 -0
- package/dist/components/task/TaskTile.js +20 -0
- package/dist/components/task/TaskTile.js.map +1 -0
- package/dist/components/task/TaskTileObjects.d.ts +13 -0
- package/dist/components/task/TaskTileObjects.js +15 -0
- package/dist/components/task/TaskTileObjects.js.map +1 -0
- package/dist/config.json +10 -10
- package/dist/controls/LookupDropdown.d.ts +11 -0
- package/dist/controls/LookupDropdown.js +39 -0
- package/dist/controls/LookupDropdown.js.map +1 -0
- package/dist/controls/index.d.ts +1 -0
- package/dist/controls/index.js +2 -0
- package/dist/controls/index.js.map +1 -0
- package/dist/enums/LogLevel.d.ts +2 -3
- package/dist/enums/LogLevel.js +1 -2
- package/dist/enums/LogLevel.js.map +1 -1
- package/dist/enums/index.d.ts +1 -0
- package/dist/enums/index.js +2 -0
- package/dist/enums/index.js.map +1 -0
- package/dist/example-code/TestifyService.js +22 -12
- package/dist/example-code/TestifyService.js.map +1 -1
- package/dist/helpers/Ensure.d.ts +2 -0
- package/dist/helpers/Ensure.js +10 -0
- package/dist/helpers/Ensure.js.map +1 -0
- package/dist/helpers/FlightTracker.d.ts +6 -0
- package/dist/helpers/FlightTracker.js +25 -0
- package/dist/helpers/FlightTracker.js.map +1 -0
- package/dist/helpers/GlobalWaitEventHandler.d.ts +12 -0
- package/dist/helpers/GlobalWaitEventHandler.js +53 -0
- package/dist/helpers/GlobalWaitEventHandler.js.map +1 -0
- package/dist/helpers/LoginHelper.d.ts +3 -2
- package/dist/helpers/LoginHelper.js +14 -8
- package/dist/helpers/LoginHelper.js.map +1 -1
- package/dist/helpers/Poll.d.ts +2 -0
- package/dist/helpers/Poll.js +17 -0
- package/dist/helpers/Poll.js.map +1 -0
- package/dist/helpers/RegexHelper.d.ts +5 -0
- package/dist/helpers/RegexHelper.js +16 -0
- package/dist/helpers/RegexHelper.js.map +1 -0
- package/dist/helpers/UserSimulationHelper.d.ts +1 -1
- package/dist/helpers/UserSimulationHelper.js +10 -6
- package/dist/helpers/UserSimulationHelper.js.map +1 -1
- package/dist/helpers/index.d.ts +2 -0
- package/dist/helpers/index.js +3 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/index.d.ts +8 -12
- package/dist/index.js +9 -13
- package/dist/index.js.map +1 -1
- package/dist/package-lock.json +4087 -0
- package/dist/package.json +57 -0
- package/dist/page-extensions/GoToDeepLink.js +15 -1
- package/dist/page-extensions/GoToDeepLink.js.map +1 -1
- package/dist/page-extensions/LoginFeatures.d.ts +2 -1
- package/dist/page-extensions/LoginFeatures.js +3 -0
- package/dist/page-extensions/LoginFeatures.js.map +1 -1
- package/dist/page-extensions/WaitEventHandler.d.ts +12 -0
- package/dist/page-extensions/WaitEventHandler.js +15 -0
- package/dist/page-extensions/WaitEventHandler.js.map +1 -0
- package/dist/page-extensions/index.d.ts +3 -0
- package/dist/page-extensions/index.js +4 -0
- package/dist/page-extensions/index.js.map +1 -0
- package/dist/page-overrides/ClickOverride.d.ts +1 -0
- package/dist/page-overrides/ClickOverride.js +46 -25
- package/dist/page-overrides/ClickOverride.js.map +1 -1
- package/dist/page-overrides/FillOverride.d.ts +7 -0
- package/dist/page-overrides/FillOverride.js +106 -0
- package/dist/page-overrides/FillOverride.js.map +1 -0
- package/dist/page-overrides/index.d.ts +1 -0
- package/dist/page-overrides/index.js +2 -0
- package/dist/page-overrides/index.js.map +1 -0
- package/dist/scripts/Testwise.template.json +25 -0
- package/dist/services/ConfigBuilder.js +23 -1
- package/dist/services/ConfigBuilder.js.map +1 -1
- package/dist/services/Logger.d.ts +1 -2
- package/dist/services/Logger.js +69 -15
- package/dist/services/Logger.js.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -0
- package/dist/tsconfig.json +20 -0
- package/dist/types/PollTypes.d.ts +6 -0
- package/dist/types/PollTypes.js +2 -0
- package/dist/types/PollTypes.js.map +1 -0
- package/dist/types/Universal.js +2 -0
- package/dist/types/Universal.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/enums/LogLevel.ts +2 -3
- package/enums/index.ts +1 -0
- package/example-code/TestifyService.ts +26 -12
- package/helpers/Ensure.ts +9 -0
- package/helpers/FlightTracker.ts +26 -0
- package/helpers/GlobalWaitEventHandler.ts +66 -0
- package/helpers/LoginHelper.ts +20 -8
- package/helpers/Poll.ts +19 -0
- package/helpers/RegexHelper.ts +17 -0
- package/helpers/UserSimulationHelper.ts +11 -6
- package/helpers/index.ts +2 -0
- package/index.ts +9 -13
- package/package.json +21 -6
- package/page-extensions/GoToDeepLink.ts +17 -1
- package/page-extensions/LoginFeatures.ts +6 -1
- package/page-extensions/WaitEventHandler.ts +26 -0
- package/page-extensions/index.ts +3 -0
- package/page-overrides/ClickOverride.ts +60 -26
- package/page-overrides/FillOverride.ts +133 -0
- package/page-overrides/index.ts +1 -0
- package/promptCredentials.js +41 -30
- package/scripts/Testwise.template.json +4 -1
- package/scripts/add-config.js +2 -2
- package/services/ConfigBuilder.ts +27 -1
- package/services/Logger.ts +73 -15
- package/services/index.ts +1 -0
- package/tsconfig.json +20 -0
- package/types/PollTypes.ts +6 -0
- package/types/index.ts +2 -0
- package/.ci/azure-pipelines.yaml +0 -80
- package/.eslintcache +0 -1
- package/.gitattributes +0 -3
- package/.gitconfig +0 -2
- package/.vscode/settings.json +0 -20
- package/components/tab/Tab.ts +0 -29
- package/components/task/TaskTiles.ts +0 -11
- package/dist/components/pop-up/PopUpComponentModels.js.map +0 -1
- package/dist/enums/ActionbarRegions.d.ts +0 -5
- package/dist/enums/ActionbarRegions.js +0 -7
- package/dist/enums/ActionbarRegions.js.map +0 -1
- package/dist/enums/ButtonEnums.d.ts +0 -24
- package/dist/enums/ButtonEnums.js +0 -29
- package/dist/enums/ButtonEnums.js.map +0 -1
- package/dist/helpers/InflightRequestTracker.d.ts +0 -10
- package/dist/helpers/InflightRequestTracker.js +0 -26
- package/dist/helpers/InflightRequestTracker.js.map +0 -1
- package/dist/types/universal.js +0 -2
- package/dist/types/universal.js.map +0 -1
- package/enums/ActionbarRegions.ts +0 -5
- package/enums/ButtonEnums.ts +0 -28
- package/helpers/InflightRequestTracker.ts +0 -33
- /package/components/pop-up/{PopUpComponentModels.ts → PopUpComponentObjects.ts} +0 -0
- /package/dist/components/pop-up/{PopUpComponentModels.d.ts → PopUpComponentObjects.d.ts} +0 -0
- /package/dist/types/{universal.d.ts → Universal.d.ts} +0 -0
- /package/types/{universal.ts → Universal.ts} +0 -0
|
@@ -14,8 +14,24 @@ export class GoToDeepLink {
|
|
|
14
14
|
page: async ({ page }, use) => {
|
|
15
15
|
page.goToDeepLink = async (deepLink: string): Promise<null | import('@playwright/test').Response> => {
|
|
16
16
|
const url = new URL(await page.url());
|
|
17
|
+
const applicationIndex = url.hash.indexOf('#application=');
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
if (applicationIndex !== -1) {
|
|
20
|
+
const applicationValue = url.hash.substring(applicationIndex + '#application='.length).split('/')[0];
|
|
21
|
+
url.hash = `#application=${applicationValue}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (deepLink.startsWith('/')) {
|
|
25
|
+
deepLink = deepLink.substring(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (url.hash.endsWith('/')) {
|
|
29
|
+
url.hash += deepLink;
|
|
30
|
+
} else {
|
|
31
|
+
url.hash += `/${deepLink}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return await page.goto(url.toString());
|
|
19
35
|
};
|
|
20
36
|
|
|
21
37
|
await use(page);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { LoginHelper } from '../helpers/LoginHelper.js';
|
|
2
2
|
import type { Test } from '../types/CoreTypes.js';
|
|
3
|
-
import type { UniversalLoginOptions } from '../types/
|
|
3
|
+
import type { UniversalLoginOptions } from '../types/index.js';
|
|
4
4
|
|
|
5
5
|
declare module '@playwright/test' {
|
|
6
6
|
interface Page {
|
|
7
7
|
logIn(): Promise<void>;
|
|
8
8
|
logInWithCredentials(username: string, password: string): Promise<void>;
|
|
9
9
|
logInWithOptions(options: UniversalLoginOptions): Promise<void>;
|
|
10
|
+
logOut(): Promise<void>;
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -31,6 +32,10 @@ export class LoginFeatures {
|
|
|
31
32
|
await this.loginHelper.logInWithOptions(options);
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
page.logOut = async () => {
|
|
36
|
+
await this.loginHelper.logOut();
|
|
37
|
+
};
|
|
38
|
+
|
|
34
39
|
await use(page);
|
|
35
40
|
}
|
|
36
41
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { GlobalWaitEventHandler } from '../helpers/GlobalWaitEventHandler.js';
|
|
2
|
+
import type { Test } from '../types/CoreTypes.js';
|
|
3
|
+
|
|
4
|
+
declare module '@playwright/test' {
|
|
5
|
+
interface Page {
|
|
6
|
+
waitEventHandler: GlobalWaitEventHandler;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class WaitEventHandler {
|
|
11
|
+
private _test: Test;
|
|
12
|
+
|
|
13
|
+
constructor(test: Test) {
|
|
14
|
+
this._test = test.extend({
|
|
15
|
+
page: async ({ page }, use) => {
|
|
16
|
+
page.waitEventHandler = new GlobalWaitEventHandler(page);
|
|
17
|
+
|
|
18
|
+
await use(page);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get test() {
|
|
24
|
+
return this._test;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,48 +1,82 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { Locator } from '@playwright/test';
|
|
2
|
+
import { FlightTracker } from '../helpers/FlightTracker.js';
|
|
3
3
|
import { testwiseConfig } from '../services/ConfigBuilder.js';
|
|
4
4
|
import type { Test } from '../types/CoreTypes.js';
|
|
5
5
|
|
|
6
|
+
const waitForRequestsToComplete =
|
|
7
|
+
testwiseConfig().get<boolean>('featureSettings.InflightRequests.waitForRequestsToComplete') ?? true;
|
|
8
|
+
const continueOnError = testwiseConfig().get<boolean>('featureSettings.InflightRequests.continueOnError') ?? true;
|
|
9
|
+
|
|
6
10
|
export class ClickOverride {
|
|
7
11
|
private _test: Test;
|
|
12
|
+
private flightTracker: FlightTracker = new FlightTracker();
|
|
8
13
|
|
|
9
14
|
constructor(test: Test) {
|
|
10
15
|
this._test = test.extend({
|
|
11
16
|
page: async ({ page }, use) => {
|
|
12
|
-
const overrideClickMethod = (locator: Locator) => {
|
|
17
|
+
const overrideClickMethod = (locator: Locator): Locator => {
|
|
13
18
|
const originalClickMethod = locator.click.bind(locator);
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
|
|
20
|
+
page.on('request', (request) => {
|
|
21
|
+
if (request.resourceType() === 'xhr') {
|
|
22
|
+
this.flightTracker.requestsInFlight++;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
page.on('response', (response) => {
|
|
27
|
+
if (response.request().resourceType() === 'xhr') {
|
|
28
|
+
if (this.flightTracker.requestsInFlight > 0) {
|
|
29
|
+
this.flightTracker.requestsInFlight--;
|
|
30
|
+
}
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
]);
|
|
32
|
+
if (this.flightTracker.requestsInFlight === 0) {
|
|
33
|
+
this.flightTracker.eventEmitter.emit('idle');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
locator.click = async (options) => {
|
|
39
|
+
await originalClickMethod(options);
|
|
40
|
+
if (waitForRequestsToComplete) {
|
|
30
41
|
try {
|
|
31
|
-
await
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} catch (e) {
|
|
37
|
-
console.warn('Exception while waiting for inflight requests to finish:', e);
|
|
38
|
-
|
|
39
|
-
if (testwiseConfig().get<boolean>('InflightRequests.swallowPollingExceptions') === false) throw e;
|
|
42
|
+
await this.flightTracker.waitToContinue();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.warn('Exception while waiting for inflight requests to finish:', error);
|
|
45
|
+
|
|
46
|
+
if (!continueOnError) throw error;
|
|
40
47
|
}
|
|
41
|
-
} else {
|
|
42
|
-
await originalClickMethod(options);
|
|
43
48
|
}
|
|
44
49
|
};
|
|
45
50
|
|
|
51
|
+
// Ensure chaining works by overriding methods that return new Locators
|
|
52
|
+
const originalLocatorMethod = locator.locator.bind(locator);
|
|
53
|
+
locator.locator = (...args) => overrideClickMethod(originalLocatorMethod(...args));
|
|
54
|
+
|
|
55
|
+
// Override the filter method so returned locators also have custom click logic
|
|
56
|
+
const originalFilterMethod = locator.filter?.bind(locator);
|
|
57
|
+
locator.filter = (...args) => overrideClickMethod(originalFilterMethod(...args));
|
|
58
|
+
|
|
59
|
+
const originalGetByTextMethod = locator.getByText.bind(locator);
|
|
60
|
+
locator.getByText = (...args) => overrideClickMethod(originalGetByTextMethod(...args));
|
|
61
|
+
|
|
62
|
+
const originalGetByTestIdMethod = locator.getByTestId.bind(locator);
|
|
63
|
+
locator.getByTestId = (...args) => overrideClickMethod(originalGetByTestIdMethod(...args));
|
|
64
|
+
|
|
65
|
+
const originalGetByAltTextMethod = locator.getByAltText.bind(locator);
|
|
66
|
+
locator.getByAltText = (...args) => overrideClickMethod(originalGetByAltTextMethod(...args));
|
|
67
|
+
|
|
68
|
+
const originalGetByLabelMethod = locator.getByLabel.bind(locator);
|
|
69
|
+
locator.getByLabel = (...args) => overrideClickMethod(originalGetByLabelMethod(...args));
|
|
70
|
+
|
|
71
|
+
const originalGetByRoleMethod = locator.getByRole.bind(locator);
|
|
72
|
+
locator.getByRole = (...args) => overrideClickMethod(originalGetByRoleMethod(...args));
|
|
73
|
+
|
|
74
|
+
const originalGetByPlaceholderMethod = locator.getByPlaceholder.bind(locator);
|
|
75
|
+
locator.getByPlaceholder = (...args) => overrideClickMethod(originalGetByPlaceholderMethod(...args));
|
|
76
|
+
|
|
77
|
+
const originalGetByTitleMethod = locator.getByTitle.bind(locator);
|
|
78
|
+
locator.getByTitle = (...args) => overrideClickMethod(originalGetByTitleMethod(...args));
|
|
79
|
+
|
|
46
80
|
return locator;
|
|
47
81
|
};
|
|
48
82
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { Locator } from '@playwright/test';
|
|
2
|
+
import { FlightTracker } from '../helpers/FlightTracker.js';
|
|
3
|
+
import { testwiseConfig } from '../services/ConfigBuilder.js';
|
|
4
|
+
import type { Test } from '../types/CoreTypes.js';
|
|
5
|
+
|
|
6
|
+
const requestWatchlist: string[] = testwiseConfig().get<string[]>('featureSettings.InflightRequests.paths') ?? [];
|
|
7
|
+
const waitForRequestsToComplete =
|
|
8
|
+
testwiseConfig().get<boolean>('featureSettings.InflightRequests.waitForRequestsToComplete') ?? true;
|
|
9
|
+
|
|
10
|
+
export class FillOverride {
|
|
11
|
+
private _test: Test;
|
|
12
|
+
private flightTracker: FlightTracker = new FlightTracker();
|
|
13
|
+
|
|
14
|
+
constructor(test: Test) {
|
|
15
|
+
this._test = test.extend({
|
|
16
|
+
page: async ({ page }, use) => {
|
|
17
|
+
if (waitForRequestsToComplete) {
|
|
18
|
+
requestWatchlist.forEach((stringToMatch) => {
|
|
19
|
+
page.on('request', (request) => {
|
|
20
|
+
if (request.url().includes(stringToMatch)) {
|
|
21
|
+
this.flightTracker.requestsInFlight++;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
page.on('response', (response) => {
|
|
26
|
+
if (response.url().includes(stringToMatch)) {
|
|
27
|
+
if (this.flightTracker.requestsInFlight > 0) this.flightTracker.requestsInFlight--;
|
|
28
|
+
|
|
29
|
+
if (this.flightTracker.requestsInFlight === 0) {
|
|
30
|
+
this.flightTracker.eventEmitter.emit('idle');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const overrideFillMethod = (locator: Locator) => {
|
|
38
|
+
const originalFillMethod = locator.fill.bind(locator);
|
|
39
|
+
|
|
40
|
+
locator.fill = async (value, options) => {
|
|
41
|
+
try {
|
|
42
|
+
if (waitForRequestsToComplete) await this.flightTracker.waitToContinue();
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.warn('Exception:', e);
|
|
45
|
+
this.flightTracker.requestsInFlight = 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const currentValue = await locator.inputValue();
|
|
49
|
+
const valueIsDifferent = currentValue !== value;
|
|
50
|
+
|
|
51
|
+
const testIdWhitelist: string[] = ['form-field__'];
|
|
52
|
+
|
|
53
|
+
const isWhitelisted = await locator.evaluate((el, testIdWhitelist) => {
|
|
54
|
+
const elementTestId = el.getAttribute('data-testid');
|
|
55
|
+
if (elementTestId) {
|
|
56
|
+
return testIdWhitelist.some((whitelistedId) => elementTestId.includes(whitelistedId));
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}, testIdWhitelist);
|
|
60
|
+
|
|
61
|
+
if (valueIsDifferent && isWhitelisted) {
|
|
62
|
+
await page.waitEventHandler.waitForCanContinue();
|
|
63
|
+
page.waitEventHandler.waitForResponse('/staged');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await originalFillMethod(value, options);
|
|
67
|
+
|
|
68
|
+
if (isWhitelisted) {
|
|
69
|
+
await locator.evaluate((el) => {
|
|
70
|
+
const ariaExpanded = el.getAttribute('aria-expanded');
|
|
71
|
+
|
|
72
|
+
if (!ariaExpanded) el.blur();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const originalLocatorMethod = locator.locator.bind(locator);
|
|
78
|
+
locator.locator = (...args) => overrideFillMethod(originalLocatorMethod(...args));
|
|
79
|
+
|
|
80
|
+
const originalGetByTextMethod = locator.getByText.bind(locator);
|
|
81
|
+
locator.getByText = (...args) => overrideFillMethod(originalGetByTextMethod(...args));
|
|
82
|
+
|
|
83
|
+
const originalGetByTestIdMethod = locator.getByTestId.bind(locator);
|
|
84
|
+
locator.getByTestId = (...args) => overrideFillMethod(originalGetByTestIdMethod(...args));
|
|
85
|
+
|
|
86
|
+
const originalGetByAltTextMethod = locator.getByAltText.bind(locator);
|
|
87
|
+
locator.getByAltText = (...args) => overrideFillMethod(originalGetByAltTextMethod(...args));
|
|
88
|
+
|
|
89
|
+
const originalGetByLabelMethod = locator.getByLabel.bind(locator);
|
|
90
|
+
locator.getByLabel = (...args) => overrideFillMethod(originalGetByLabelMethod(...args));
|
|
91
|
+
|
|
92
|
+
const originalGetByRoleMethod = locator.getByRole.bind(locator);
|
|
93
|
+
locator.getByRole = (...args) => overrideFillMethod(originalGetByRoleMethod(...args));
|
|
94
|
+
|
|
95
|
+
const originalGetByPlaceholderMethod = locator.getByPlaceholder.bind(locator);
|
|
96
|
+
locator.getByPlaceholder = (...args) => overrideFillMethod(originalGetByPlaceholderMethod(...args));
|
|
97
|
+
|
|
98
|
+
const originalGetByTitleMethod = locator.getByTitle.bind(locator);
|
|
99
|
+
locator.getByTitle = (...args) => overrideFillMethod(originalGetByTitleMethod(...args));
|
|
100
|
+
|
|
101
|
+
return locator;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Override the locator method on the page object
|
|
105
|
+
const originalLocator = page.locator.bind(page);
|
|
106
|
+
page.locator = (...args) => overrideFillMethod(originalLocator(...args));
|
|
107
|
+
|
|
108
|
+
const originalGetByTestId = page.getByTestId.bind(page);
|
|
109
|
+
const originalGetByAltText = page.getByAltText.bind(page);
|
|
110
|
+
const originalGetByLabel = page.getByLabel.bind(page);
|
|
111
|
+
const originalGetByRole = page.getByRole.bind(page);
|
|
112
|
+
const originalGetByPlaceholder = page.getByPlaceholder.bind(page);
|
|
113
|
+
const originalGetByText = page.getByText.bind(page);
|
|
114
|
+
const originalGetByTitle = page.getByTitle.bind(page);
|
|
115
|
+
|
|
116
|
+
// Override other top-level methods as before
|
|
117
|
+
page.getByTestId = (...args) => overrideFillMethod(originalGetByTestId(...args));
|
|
118
|
+
page.getByAltText = (...args) => overrideFillMethod(originalGetByAltText(...args));
|
|
119
|
+
page.getByLabel = (...args) => overrideFillMethod(originalGetByLabel(...args));
|
|
120
|
+
page.getByRole = (...args) => overrideFillMethod(originalGetByRole(...args));
|
|
121
|
+
page.getByPlaceholder = (...args) => overrideFillMethod(originalGetByPlaceholder(...args));
|
|
122
|
+
page.getByText = (...args) => overrideFillMethod(originalGetByText(...args));
|
|
123
|
+
page.getByTitle = (...args) => overrideFillMethod(originalGetByTitle(...args));
|
|
124
|
+
|
|
125
|
+
await use(page);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get test() {
|
|
131
|
+
return this._test;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ClickOverride.js';
|
package/promptCredentials.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import process from 'node:process';
|
|
4
2
|
import { spawnSync } from 'node:child_process';
|
|
5
3
|
import { readFileSync } from 'node:fs';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
6
|
import prompts from 'prompts';
|
|
7
7
|
|
|
8
8
|
const packageScripts = JSON.parse(readFileSync('./package.json')).scripts;
|
|
@@ -14,7 +14,10 @@ function exitWithError(error) {
|
|
|
14
14
|
|
|
15
15
|
function startTestSolution() {
|
|
16
16
|
console.log('Now starting test solution...');
|
|
17
|
-
spawnSync(`yarn --cwd ../ ${args.join(' ')}`, {
|
|
17
|
+
spawnSync(`yarn --cwd ../ ${args.join(' ')}`, {
|
|
18
|
+
shell: true,
|
|
19
|
+
stdio: 'inherit'
|
|
20
|
+
});
|
|
18
21
|
process.exit();
|
|
19
22
|
}
|
|
20
23
|
|
|
@@ -53,27 +56,30 @@ if (process.env.SF_TEST_USERNAME && process.env.SF_TEST_PASSWORD) {
|
|
|
53
56
|
startTestSolution();
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
const questions = [
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const questions = [
|
|
60
|
+
{
|
|
61
|
+
type: 'text',
|
|
62
|
+
name: 'username',
|
|
63
|
+
message: 'Username:',
|
|
64
|
+
validate: (value) => {
|
|
65
|
+
if (!value?.length) {
|
|
66
|
+
return 'Username is required';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (value.toLowerCase() === 'demo') {
|
|
70
|
+
return 'Running tests locally with demo is forbidden as it results in dataset concurrency problems';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return true;
|
|
63
74
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'invisible',
|
|
78
|
+
name: 'password',
|
|
79
|
+
message: 'Password:',
|
|
80
|
+
validate: (value) => (!value?.length ? 'Password is required' : true)
|
|
70
81
|
}
|
|
71
|
-
|
|
72
|
-
type: 'invisible',
|
|
73
|
-
name: 'password',
|
|
74
|
-
message: 'Password:',
|
|
75
|
-
validate: (value) => !value?.length ? 'Password is required' : true
|
|
76
|
-
}];
|
|
82
|
+
];
|
|
77
83
|
|
|
78
84
|
(async () => {
|
|
79
85
|
const credentialsResponse = await prompts(questions);
|
|
@@ -89,17 +95,22 @@ const questions = [{
|
|
|
89
95
|
params.append('Password', password);
|
|
90
96
|
|
|
91
97
|
const fetch = (await import('node-fetch')).default;
|
|
92
|
-
const authResponse = await fetch(`${indiciumURL}/account/api/login`, {
|
|
98
|
+
const authResponse = await fetch(`${indiciumURL}/account/api/login`, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
body: params
|
|
101
|
+
});
|
|
93
102
|
|
|
94
103
|
if (authResponse.status === 204) {
|
|
95
104
|
console.log('Confirmed credentials work @', indiciumURL);
|
|
96
105
|
} else {
|
|
97
|
-
const confirmResponse = await prompts([
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
const confirmResponse = await prompts([
|
|
107
|
+
{
|
|
108
|
+
type: 'confirm',
|
|
109
|
+
name: 'value',
|
|
110
|
+
message: `Can't login to ${indiciumURL} with the given credentials. Continue anyways?`,
|
|
111
|
+
initial: true
|
|
112
|
+
}
|
|
113
|
+
]);
|
|
103
114
|
|
|
104
115
|
if (!confirmResponse.value) {
|
|
105
116
|
return;
|
|
@@ -110,4 +121,4 @@ const questions = [{
|
|
|
110
121
|
process.env.SF_TEST_PASSWORD = password;
|
|
111
122
|
|
|
112
123
|
startTestSolution();
|
|
113
|
-
})();
|
|
124
|
+
})();
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
"logger" : {
|
|
9
9
|
"logDir" : "logs",
|
|
10
10
|
"logLevel" : "info",
|
|
11
|
+
"applyLogLevelToConsoleOutput" : false,
|
|
11
12
|
"logByDay" : true,
|
|
12
13
|
"bufferSizeLimit" : -1
|
|
13
14
|
},
|
|
14
15
|
"InflightRequests" : {
|
|
15
16
|
"waitForRequestsToComplete" : true,
|
|
16
|
-
"
|
|
17
|
+
"waitTimeout" : 5000,
|
|
18
|
+
"continueOnError" : true,
|
|
19
|
+
"paths" : ["/stage", "/layout()", "/context()"]
|
|
17
20
|
},
|
|
18
21
|
"other" : {
|
|
19
22
|
"loginTimeout" : 30000
|
package/scripts/add-config.js
CHANGED
|
@@ -11,7 +11,7 @@ if (process.argv[2] === 'install') {
|
|
|
11
11
|
const __dirname = path.dirname(__filename);
|
|
12
12
|
const configFile = path.resolve(process.cwd(), 'Testwise.json');
|
|
13
13
|
const templateFile = path.join(__dirname, 'Testwise.template.json');
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
if (!fs.existsSync(configFile)) {
|
|
16
16
|
fs.copyFileSync(templateFile, configFile);
|
|
17
17
|
console.log('Testwise.json config file created.');
|
|
@@ -20,4 +20,4 @@ if (process.argv[2] === 'install') {
|
|
|
20
20
|
}
|
|
21
21
|
} else {
|
|
22
22
|
console.log('Unknown or missing command. Usage: npx testwise install');
|
|
23
|
-
}
|
|
23
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import dotenv from 'dotenv';
|
|
3
4
|
|
|
4
5
|
class ConfigBuilder {
|
|
5
6
|
private config: Record<string, unknown>;
|
|
@@ -7,6 +8,14 @@ class ConfigBuilder {
|
|
|
7
8
|
constructor() {
|
|
8
9
|
const configFilePath = path.resolve(process.cwd(), 'Testwise.json');
|
|
9
10
|
|
|
11
|
+
// Find all env files in the consuming project and apply them to process.env
|
|
12
|
+
const envFiles = fs.readdirSync(process.cwd()).filter((file) => file.endsWith('.env'));
|
|
13
|
+
|
|
14
|
+
for (const envFile of envFiles) {
|
|
15
|
+
const envConfig = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), envFile)));
|
|
16
|
+
Object.assign(process.env, envConfig);
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
if (!fs.existsSync(configFilePath)) {
|
|
11
20
|
throw new Error(`Config file not found at path: ${configFilePath}`);
|
|
12
21
|
}
|
|
@@ -21,12 +30,29 @@ class ConfigBuilder {
|
|
|
21
30
|
* @returns The value associated with the key, or undefined if not found.
|
|
22
31
|
*/
|
|
23
32
|
public get<T>(key: string): T | undefined {
|
|
24
|
-
|
|
33
|
+
const result = key
|
|
25
34
|
.split('.')
|
|
26
35
|
.reduce(
|
|
27
36
|
(obj: Record<string, unknown> | undefined, segment) => obj?.[segment] as Record<string, unknown>,
|
|
28
37
|
this.config
|
|
29
38
|
) as T;
|
|
39
|
+
|
|
40
|
+
// Convert the key to uppercase, replace dots with underscores, and insert underscores before uppercase letters
|
|
41
|
+
let envKey = key.replace(/\./g, '_');
|
|
42
|
+
envKey = envKey.replace(/([a-z0-9])([A-Z])/g, '$1_$2');
|
|
43
|
+
envKey = envKey.toUpperCase();
|
|
44
|
+
|
|
45
|
+
if (process.env[envKey] !== undefined) {
|
|
46
|
+
const envValue = process.env[envKey] as unknown;
|
|
47
|
+
// Attempt to parse JSON values, fallback to string if parsing fails
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(envValue as string) as T;
|
|
50
|
+
} catch {
|
|
51
|
+
return envValue as T;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result;
|
|
30
56
|
}
|
|
31
57
|
}
|
|
32
58
|
|
package/services/Logger.ts
CHANGED
|
@@ -5,6 +5,46 @@ import { originalDebug, originalError, originalInfo, originalLog, originalWarn }
|
|
|
5
5
|
import { testwiseConfig } from './ConfigBuilder.js';
|
|
6
6
|
|
|
7
7
|
const logDir = path.resolve(process.cwd(), testwiseConfig().get<string>('featureSettings.logger.logDir') ?? 'logs');
|
|
8
|
+
const logLevel =
|
|
9
|
+
(testwiseConfig().get<string>('featureSettings.logger.logLevel')?.toUpperCase() as LogLevel) ?? LogLevel.ALL;
|
|
10
|
+
const applyLogLevelToConsoleOutput =
|
|
11
|
+
testwiseConfig().get<boolean>('featureSettings.logger.applyLogLevelToConsoleOutput') ?? false;
|
|
12
|
+
|
|
13
|
+
let logErrors = false;
|
|
14
|
+
let logWarnings = false;
|
|
15
|
+
let logInfo = false;
|
|
16
|
+
let logDebug = false;
|
|
17
|
+
let logLog = false;
|
|
18
|
+
|
|
19
|
+
switch (logLevel) {
|
|
20
|
+
case LogLevel.ERROR:
|
|
21
|
+
logErrors = true;
|
|
22
|
+
break;
|
|
23
|
+
case LogLevel.WARN:
|
|
24
|
+
logErrors = true;
|
|
25
|
+
logWarnings = true;
|
|
26
|
+
break;
|
|
27
|
+
case LogLevel.INFO:
|
|
28
|
+
logErrors = true;
|
|
29
|
+
logWarnings = true;
|
|
30
|
+
logInfo = true;
|
|
31
|
+
break;
|
|
32
|
+
case LogLevel.DEBUG:
|
|
33
|
+
logErrors = true;
|
|
34
|
+
logWarnings = true;
|
|
35
|
+
logInfo = true;
|
|
36
|
+
logDebug = true;
|
|
37
|
+
break;
|
|
38
|
+
case LogLevel.ALL:
|
|
39
|
+
logErrors = true;
|
|
40
|
+
logWarnings = true;
|
|
41
|
+
logInfo = true;
|
|
42
|
+
logDebug = true;
|
|
43
|
+
logLog = true;
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
throw new Error(`Unknown log level: ${logLevel}`);
|
|
47
|
+
}
|
|
8
48
|
|
|
9
49
|
fs.mkdirSync(logDir, { recursive: true });
|
|
10
50
|
|
|
@@ -19,12 +59,12 @@ const logFilePath = path.join(
|
|
|
19
59
|
let logBuffer: string[] = [];
|
|
20
60
|
const bufferSizeLimit = testwiseConfig().get<number>('featureSettings.logger.bufferSizeLimit') ?? 1000;
|
|
21
61
|
|
|
22
|
-
let
|
|
62
|
+
let _linesAlreadyWritten = 0;
|
|
23
63
|
|
|
24
64
|
if (fs.existsSync(logFilePath)) {
|
|
25
65
|
const existingLogs = fs.readFileSync(logFilePath, 'utf-8').split('\n').filter(Boolean);
|
|
26
66
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
27
|
-
|
|
67
|
+
_linesAlreadyWritten = existingLogs.length;
|
|
28
68
|
}
|
|
29
69
|
|
|
30
70
|
/**
|
|
@@ -57,32 +97,51 @@ function flushLogs() {
|
|
|
57
97
|
* @param level
|
|
58
98
|
* @param optionalParams
|
|
59
99
|
*/
|
|
60
|
-
function log(message?: string, level: LogLevel = LogLevel.
|
|
100
|
+
function log(message?: string, level: LogLevel = LogLevel.ALL, ...optionalParams: unknown[]): void {
|
|
61
101
|
const timestamp = new Date().toISOString();
|
|
62
102
|
const origin = getCallerInfo();
|
|
63
103
|
const formatted = `[${timestamp}] [${level}] [${origin}] ${message}`;
|
|
64
104
|
|
|
65
105
|
switch (level) {
|
|
66
|
-
case LogLevel.FATAL:
|
|
67
106
|
case LogLevel.ERROR:
|
|
68
|
-
|
|
107
|
+
if (logErrors) {
|
|
108
|
+
logBuffer.push(`${formatted} ${optionalParams}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (logErrors || !applyLogLevelToConsoleOutput) originalError(formatted, ...optionalParams);
|
|
69
112
|
break;
|
|
70
113
|
case LogLevel.WARN:
|
|
71
|
-
|
|
114
|
+
if (logWarnings) {
|
|
115
|
+
logBuffer.push(`${formatted} ${optionalParams}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (logWarnings || !applyLogLevelToConsoleOutput) originalWarn(formatted, ...optionalParams);
|
|
72
119
|
break;
|
|
73
120
|
case LogLevel.DEBUG:
|
|
74
|
-
|
|
121
|
+
if (logDebug) {
|
|
122
|
+
logBuffer.push(`${formatted} ${optionalParams}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (logDebug || !applyLogLevelToConsoleOutput) originalDebug(formatted, ...optionalParams);
|
|
75
126
|
break;
|
|
76
127
|
case LogLevel.INFO:
|
|
77
|
-
|
|
128
|
+
if (logInfo) {
|
|
129
|
+
logBuffer.push(`${formatted} ${optionalParams}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (logInfo || !applyLogLevelToConsoleOutput) originalInfo(formatted, ...optionalParams);
|
|
78
133
|
break;
|
|
79
134
|
default:
|
|
80
|
-
|
|
81
|
-
|
|
135
|
+
if (logLog) {
|
|
136
|
+
logBuffer.push(`${formatted} ${optionalParams}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (logLog || !applyLogLevelToConsoleOutput) originalLog(formatted, ...optionalParams);
|
|
82
140
|
}
|
|
83
141
|
|
|
84
|
-
logBuffer.
|
|
85
|
-
|
|
142
|
+
if (logBuffer.length >= bufferSizeLimit) {
|
|
143
|
+
flushLogs();
|
|
144
|
+
}
|
|
86
145
|
}
|
|
87
146
|
|
|
88
147
|
/**
|
|
@@ -116,10 +175,9 @@ function getCallerInfo(): string {
|
|
|
116
175
|
|
|
117
176
|
export const logger = {
|
|
118
177
|
// Order by severity level
|
|
119
|
-
fatal: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.FATAL, ...optionalParams),
|
|
120
178
|
error: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.ERROR, ...optionalParams),
|
|
121
179
|
warn: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.WARN, ...optionalParams),
|
|
122
|
-
step: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.STEP, ...optionalParams),
|
|
123
180
|
info: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.INFO, ...optionalParams),
|
|
124
|
-
debug: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.DEBUG, ...optionalParams)
|
|
181
|
+
debug: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.DEBUG, ...optionalParams),
|
|
182
|
+
log: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.ALL, ...optionalParams)
|
|
125
183
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ConfigBuilder.js';
|