@testspectra/cli 1.0.70 → 1.1.1
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/LICENSE.md +48 -0
- package/README.md +36 -270
- package/bin/.testspectra-runner.hash +1 -0
- package/bin/spectra.js +1 -1
- package/bin/testspectra-runner +0 -0
- package/demo-app/assets/index-DPfv5_EF.js +116 -0
- package/demo-app/assets/index-gIwu2EMV.css +1 -0
- package/demo-app/index.html +2 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/commands/__tests__/cloud-run.test.d.ts +1 -0
- package/dist/commands/__tests__/cloud-run.test.js +93 -0
- package/dist/commands/__tests__/doctor.test.d.ts +1 -0
- package/dist/commands/__tests__/doctor.test.js +39 -0
- package/dist/commands/__tests__/init.test.d.ts +1 -0
- package/dist/commands/__tests__/init.test.js +148 -0
- package/dist/commands/__tests__/license.test.d.ts +1 -0
- package/dist/commands/__tests__/license.test.js +13 -0
- package/dist/commands/__tests__/refactor.test.d.ts +1 -0
- package/dist/commands/__tests__/refactor.test.js +64 -0
- package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
- package/dist/commands/__tests__/run-e2e.test.js +181 -0
- package/dist/commands/__tests__/run.test.d.ts +1 -0
- package/dist/commands/__tests__/run.test.js +197 -0
- package/dist/commands/__tests__/watch.test.d.ts +1 -0
- package/dist/commands/__tests__/watch.test.js +155 -0
- package/dist/commands/add.js +86 -85
- package/dist/commands/cloud-run.d.ts +11 -0
- package/dist/commands/cloud-run.js +118 -0
- package/dist/commands/demo.js +7 -7
- package/dist/commands/devices.d.ts +14 -2
- package/dist/commands/devices.js +44 -34
- package/dist/commands/doctor.d.ts +3 -1
- package/dist/commands/doctor.js +43 -264
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +206 -196
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.js +59 -0
- package/dist/commands/license.d.ts +4 -0
- package/dist/commands/license.js +30 -0
- package/dist/commands/refactor.d.ts +8 -0
- package/dist/commands/refactor.js +78 -0
- package/dist/commands/run.d.ts +22 -1
- package/dist/commands/run.js +252 -109
- package/dist/commands/sync-types.d.ts +4 -0
- package/dist/commands/sync-types.js +14 -0
- package/dist/commands/update.js +39 -43
- package/dist/commands/watch.d.ts +6 -0
- package/dist/commands/watch.js +47 -43
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +40 -25
- package/dist/config/schema.d.ts +117 -49
- package/dist/config/schema.js +41 -34
- package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
- package/dist/generator/__tests__/tsconfig-isolation.test.js +278 -0
- package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
- package/dist/generator/__tests__/type-generator.test.js +386 -0
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +2 -0
- package/dist/generator/tsconfig-generator.d.ts +27 -0
- package/dist/generator/tsconfig-generator.js +391 -0
- package/dist/generator/type-generator.d.ts +36 -0
- package/dist/generator/type-generator.js +644 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +124 -51
- package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
- package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
- package/dist/lifecycle/hook-discovery.d.ts +24 -0
- package/dist/lifecycle/hook-discovery.js +60 -0
- package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
- package/dist/lifecycle/hook-dispatcher.js +190 -0
- package/dist/lifecycle/index.d.ts +3 -0
- package/dist/lifecycle/index.js +3 -0
- package/dist/lifecycle/parallel-grouping.d.ts +27 -0
- package/dist/lifecycle/parallel-grouping.js +151 -0
- package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
- package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
- package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
- package/dist/mobile/__tests__/device-pool.test.js +97 -0
- package/dist/mobile/device-pool.d.ts +23 -0
- package/dist/mobile/device-pool.js +1 -0
- package/dist/mobile/index.d.ts +1 -0
- package/dist/mobile/index.js +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.js +175 -0
- package/dist/refactor/index.d.ts +1 -0
- package/dist/refactor/index.js +1 -0
- package/dist/refactor/refactor-engine.d.ts +25 -0
- package/dist/refactor/refactor-engine.js +255 -0
- package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
- package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
- package/dist/reporter/semantic-formatter.d.ts +6 -0
- package/dist/reporter/semantic-formatter.js +191 -0
- package/dist/reporter/summary-view.d.ts +12 -0
- package/dist/reporter/summary-view.js +19 -0
- package/dist/reporter/terminal-stream.d.ts +32 -0
- package/dist/reporter/terminal-stream.js +82 -0
- package/dist/reporter/types.d.ts +2 -0
- package/dist/reporter/types.js +1 -0
- package/dist/runner/bridge.d.ts +10 -5
- package/dist/runner/bridge.js +69 -44
- package/dist/runner/reporter.d.ts +30 -2
- package/dist/runner/reporter.js +419 -13
- package/dist/server/demo.js +43 -44
- package/dist/types/generator.d.ts +1 -17
- package/dist/types/generator.js +1 -443
- package/dist/utils/demo-server.js +38 -35
- package/package.json +20 -12
- package/templates/default/CLAUDE.md +7 -5
- package/templates/default/fixtures/apiData.json +24 -0
- package/templates/default/package.json +13 -4
- package/templates/default/page-objects/MatchersPage/mobile.ts +41 -16
- package/templates/default/page-objects/MatchersPage/web.ts +14 -0
- package/templates/default/page-objects/NavigationPage/mobile.ts +10 -1
- package/templates/default/page-objects/PlaygroundPage/mobile.ts +27 -17
- package/templates/default/page-objects/PlaygroundPage/web.ts +2 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +11 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +16 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
- package/templates/default/specs/ApiInterception/suite.md +9 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +15 -1
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
- package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
- package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
- package/templates/default/spectra.config.ts +21 -14
- package/templates/default/support/actions/fillCredentials/mobile.action.ts +2 -2
- package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +2 -0
- package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +2 -2
- package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +3 -1
- package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +3 -3
- package/templates/default/tsconfig.json +3 -1
- package/templates/nx/.nx/workspace-data/d/daemon.log +168 -0
- package/templates/nx/CLAUDE.md +2 -2
- package/templates/nx/package.json +7 -5
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +3 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +3 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
- package/templates/nx/pnpm-lock.yaml +6663 -0
- package/templates/nx/pnpm-workspace.yaml +2 -1
- package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
- package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +12 -0
- package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +14 -0
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +2 -0
- package/templates/nx/shared/testing/project.json +19 -0
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +2 -2
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +1 -1
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +3 -3
- package/templates/nx/spectra.config.ts +19 -12
- package/templates/nx/tsconfig.json +3 -1
- package/demo-app/assets/index-DAUlrHTr.css +0 -1
- package/demo-app/assets/index-DN2Kb0A8.js +0 -100
- package/dist/index.cjs +0 -101
- package/dist/plugin.cjs +0 -575
- package/dist/plugin.d.ts +0 -16
- package/dist/plugin.js +0 -184
- package/dist/step/index.d.ts +0 -6
- package/dist/step/index.js +0 -6
- package/dist/step/matchers.d.ts +0 -14
- package/dist/step/matchers.js +0 -114
- package/dist/step/proto.d.ts +0 -46
- package/dist/step/proto.js +0 -114
- package/dist/step/runner/collection.d.ts +0 -13
- package/dist/step/runner/collection.js +0 -51
- package/dist/step/runner/single.d.ts +0 -21
- package/dist/step/runner/single.js +0 -138
- package/dist/step/spectra.d.ts +0 -34
- package/dist/step/spectra.js +0 -156
- package/dist/step/types.d.ts +0 -32
- package/dist/step/types.js +0 -5
- package/templates/default/tsconfig.spectra.android.json +0 -46
- package/templates/default/tsconfig.spectra.ios.json +0 -46
- package/templates/default/tsconfig.spectra.json +0 -22
- package/templates/default/tsconfig.spectra.web.json +0 -53
- package/templates/nx/fixtures/playgroundData.json +0 -7
- package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
- package/templates/nx/global-hooks/after/web.hook.ts +0 -3
- package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
- package/templates/nx/global-hooks/before/web.hook.ts +0 -3
- package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
- package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
- package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
- package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
- package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
- package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
- package/templates/nx/specs/BrowserContext/suite.md +0 -9
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
- package/templates/nx/specs/ContentMatchers/suite.md +0 -9
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
- package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/suite.md +0 -9
- package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
- package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
- package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
- package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/suite.md +0 -9
- package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
- package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
- package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
- package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
- package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
- package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
- package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
- package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
- package/templates/nx/tsconfig.spectra.android.json +0 -39
- package/templates/nx/tsconfig.spectra.ios.json +0 -39
- package/templates/nx/tsconfig.spectra.json +0 -25
- package/templates/nx/tsconfig.spectra.shared.json +0 -25
- package/templates/nx/tsconfig.spectra.web.json +0 -45
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
function extractTargetString(target) {
|
|
2
|
+
if (!target)
|
|
3
|
+
return '';
|
|
4
|
+
if (typeof target === 'string')
|
|
5
|
+
return target;
|
|
6
|
+
if (typeof target === 'object') {
|
|
7
|
+
if (typeof target.selector === 'string')
|
|
8
|
+
return target.selector;
|
|
9
|
+
if (typeof target.name === 'string')
|
|
10
|
+
return target.name;
|
|
11
|
+
if (typeof target.target === 'string')
|
|
12
|
+
return target.target;
|
|
13
|
+
}
|
|
14
|
+
return String(target);
|
|
15
|
+
}
|
|
16
|
+
function formatElementTarget(target) {
|
|
17
|
+
const sel = extractTargetString(target);
|
|
18
|
+
if (!sel)
|
|
19
|
+
return 'element {target}';
|
|
20
|
+
if (sel.startsWith('element "') || sel.startsWith('collection "')) {
|
|
21
|
+
return sel;
|
|
22
|
+
}
|
|
23
|
+
return `element "${sel}"`;
|
|
24
|
+
}
|
|
25
|
+
function formatCollectionTarget(target) {
|
|
26
|
+
const sel = extractTargetString(target);
|
|
27
|
+
if (!sel)
|
|
28
|
+
return 'collection "{selector}"';
|
|
29
|
+
if (sel.startsWith('collection "')) {
|
|
30
|
+
return sel;
|
|
31
|
+
}
|
|
32
|
+
return `collection "${sel}"`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Transforms raw matcher assertions and element commands into grammatically correct English sentences.
|
|
36
|
+
*/
|
|
37
|
+
export function formatAction(actionKey, target, args = {}) {
|
|
38
|
+
const el = formatElementTarget(target);
|
|
39
|
+
switch (actionKey) {
|
|
40
|
+
case 'click':
|
|
41
|
+
return `Click on ${el}`;
|
|
42
|
+
case 'doubleClick':
|
|
43
|
+
return `Double-click on ${el}`;
|
|
44
|
+
case 'rightClick':
|
|
45
|
+
return `Right-click on ${el}`;
|
|
46
|
+
case 'type':
|
|
47
|
+
return `Type "${args.text ?? args.value ?? '{text}'}" into ${el}`;
|
|
48
|
+
case 'clear':
|
|
49
|
+
return `Clear text in ${el}`;
|
|
50
|
+
case 'select':
|
|
51
|
+
case 'selectOption':
|
|
52
|
+
return `Select option "${args.option ?? args.value ?? args.text ?? '{option}'}" in ${el}`;
|
|
53
|
+
case 'hover':
|
|
54
|
+
return `Hover over ${el}`;
|
|
55
|
+
case 'dragDrop':
|
|
56
|
+
const dest = args.dest || args.destination ? formatElementTarget(args.dest || args.destination) : 'element {dest}';
|
|
57
|
+
return `Drag ${el} and drop onto ${dest}`;
|
|
58
|
+
case 'scrollIntoView':
|
|
59
|
+
return `Scroll ${el} into view`;
|
|
60
|
+
case 'longPress':
|
|
61
|
+
return `Long-press on ${el} for ${args.durationMs ?? args.ms ?? args.duration ?? '{durationMs}'}ms`;
|
|
62
|
+
case 'waitForElement':
|
|
63
|
+
return `Wait for ${el} to be displayed`;
|
|
64
|
+
case 'navigate':
|
|
65
|
+
return `Navigate to "${args.url ?? '{url}'}"`;
|
|
66
|
+
case 'back':
|
|
67
|
+
return `Navigate back in browser`;
|
|
68
|
+
case 'forward':
|
|
69
|
+
return `Navigate forward in browser`;
|
|
70
|
+
case 'refresh':
|
|
71
|
+
return `Refresh current page`;
|
|
72
|
+
case 'setViewport':
|
|
73
|
+
return `Set viewport to ${args.width ?? '{width}'}x${args.height ?? '{height}'}`;
|
|
74
|
+
case 'wait':
|
|
75
|
+
return `Wait for ${args.ms ?? args.durationMs ?? '{ms}'}ms`;
|
|
76
|
+
case 'pressKey':
|
|
77
|
+
return `Press keyboard key "${args.key ?? args.value ?? '{key}'}"`;
|
|
78
|
+
case 'scroll':
|
|
79
|
+
return `Scroll ${args.direction ?? '{direction}'} by ${args.pixels ?? '{pixels}'}px`;
|
|
80
|
+
case 'swipe':
|
|
81
|
+
return `Swipe ${args.direction ?? '{direction}'} by ${args.distance ?? '{distance}'}px`;
|
|
82
|
+
case 'intercept':
|
|
83
|
+
return `Intercept ${args.method ?? '{method}'} "${args.url ?? '{url}'}" mock rule registered`;
|
|
84
|
+
case 'clearCookies':
|
|
85
|
+
return `Clear all browser cookies`;
|
|
86
|
+
case 'clearLocalStorage':
|
|
87
|
+
return `Clear browser localStorage`;
|
|
88
|
+
default:
|
|
89
|
+
return `Perform ${actionKey} on ${el}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function formatAssertion(assertionKey, target, args = {}) {
|
|
93
|
+
const el = formatElementTarget(target);
|
|
94
|
+
const col = formatCollectionTarget(target);
|
|
95
|
+
switch (assertionKey) {
|
|
96
|
+
case 'be.visible':
|
|
97
|
+
return `Expect ${el} to be visible`;
|
|
98
|
+
case 'not.be.visible':
|
|
99
|
+
return `Expect ${el} not to be visible`;
|
|
100
|
+
case 'exist':
|
|
101
|
+
return `Expect ${el} to exist in DOM`;
|
|
102
|
+
case 'not.exist':
|
|
103
|
+
return `Expect ${el} not to exist in DOM`;
|
|
104
|
+
case 'be.clickable':
|
|
105
|
+
return `Expect ${el} to be clickable`;
|
|
106
|
+
case 'not.be.clickable':
|
|
107
|
+
return `Expect ${el} not to be clickable`;
|
|
108
|
+
case 'be.enabled':
|
|
109
|
+
return `Expect ${el} to be enabled`;
|
|
110
|
+
case 'be.disabled':
|
|
111
|
+
return `Expect ${el} to be disabled`;
|
|
112
|
+
case 'be.checked':
|
|
113
|
+
return `Expect ${el} to be checked`;
|
|
114
|
+
case 'not.be.checked':
|
|
115
|
+
return `Expect ${el} not to be checked`;
|
|
116
|
+
case 'be.selected':
|
|
117
|
+
return `Expect ${el} to be selected`;
|
|
118
|
+
case 'not.be.selected':
|
|
119
|
+
return `Expect ${el} not to be selected`;
|
|
120
|
+
case 'be.focused':
|
|
121
|
+
return `Expect ${el} to be focused`;
|
|
122
|
+
case 'not.be.focused':
|
|
123
|
+
return `Expect ${el} not to be focused`;
|
|
124
|
+
case 'have.text':
|
|
125
|
+
return `Expect ${el} to have text "${args.expected ?? args.text ?? '{str}'}"`;
|
|
126
|
+
case 'not.have.text':
|
|
127
|
+
return `Expect ${el} not to have text "${args.expected ?? args.text ?? '{str}'}"`;
|
|
128
|
+
case 'contain.text':
|
|
129
|
+
return `Expect ${el} to contain text "${args.expected ?? args.text ?? '{sub}'}"`;
|
|
130
|
+
case 'not.contain.text':
|
|
131
|
+
return `Expect ${el} not to contain text "${args.expected ?? args.text ?? '{s}'}"`;
|
|
132
|
+
case 'have.value':
|
|
133
|
+
return `Expect ${el} to have value "${args.expected ?? args.value ?? '{val}'}"`;
|
|
134
|
+
case 'not.have.value':
|
|
135
|
+
return `Expect ${el} not to have value "${args.expected ?? args.value ?? '{val}'}"`;
|
|
136
|
+
case 'contain.value':
|
|
137
|
+
return `Expect ${el} to contain value "${args.expected ?? args.value ?? '{s}'}"`;
|
|
138
|
+
case 'not.contain.value':
|
|
139
|
+
return `Expect ${el} not to contain value "${args.expected ?? args.value ?? '{s}'}"`;
|
|
140
|
+
case 'have.attr':
|
|
141
|
+
return args.value !== undefined
|
|
142
|
+
? `Expect ${el} to have attribute "${args.name ?? args.expected ?? '{k}'}" = "${args.value}"`
|
|
143
|
+
: `Expect ${el} to have attribute "${args.name ?? args.expected ?? '{k}'}"`;
|
|
144
|
+
case 'not.have.attr':
|
|
145
|
+
return `Expect ${el} not to have attribute "${args.name ?? args.expected ?? '{k}'}"`;
|
|
146
|
+
case 'have.class':
|
|
147
|
+
return `Expect ${el} to have class "${args.expected ?? args.className ?? '{cls}'}"`;
|
|
148
|
+
case 'not.have.class':
|
|
149
|
+
return `Expect ${el} not to have class "${args.expected ?? args.className ?? '{cls}'}"`;
|
|
150
|
+
case 'have.css':
|
|
151
|
+
return `Expect ${el} to have CSS "${args.name ?? args.property ?? '{prop}'}" = "${args.value ?? '{val}'}"`;
|
|
152
|
+
case 'not.have.css':
|
|
153
|
+
return `Expect ${el} not to have CSS "${args.name ?? args.property ?? '{p}'}" = "${args.value ?? '{v}'}"`;
|
|
154
|
+
// Collections
|
|
155
|
+
case 'have.length':
|
|
156
|
+
return `Expect ${col} count to equal ${args.expected ?? args.length ?? '{n}'}`;
|
|
157
|
+
case 'not.have.length':
|
|
158
|
+
return `Expect ${col} count not to equal ${args.expected ?? args.length ?? '{n}'}`;
|
|
159
|
+
case 'have.length.greaterThan':
|
|
160
|
+
return `Expect ${col} count to be greater than ${args.expected ?? args.min ?? '{n}'}`;
|
|
161
|
+
case 'have.length.lessThan':
|
|
162
|
+
return `Expect ${col} count to be less than ${args.expected ?? args.max ?? '{n}'}`;
|
|
163
|
+
case 'be.empty':
|
|
164
|
+
return `Expect ${col} to be empty`;
|
|
165
|
+
case 'not.be.empty':
|
|
166
|
+
return `Expect ${col} not to be empty`;
|
|
167
|
+
// Browser Context
|
|
168
|
+
case 'have.url':
|
|
169
|
+
case 'shouldHaveUrl':
|
|
170
|
+
return `Expect browser URL to be "${args.expected ?? args.url ?? '{url}'}"`;
|
|
171
|
+
case 'contain.url':
|
|
172
|
+
case 'shouldContainUrl':
|
|
173
|
+
return `Expect browser URL to contain "${args.expected ?? args.url ?? '{url}'}"`;
|
|
174
|
+
case 'have.title':
|
|
175
|
+
case 'shouldHaveTitle':
|
|
176
|
+
return `Expect browser title to be "${args.expected ?? args.title ?? '{title}'}"`;
|
|
177
|
+
case 'contain.title':
|
|
178
|
+
case 'shouldContainTitle':
|
|
179
|
+
return `Expect browser title to contain "${args.expected ?? args.title ?? '{title}'}"`;
|
|
180
|
+
case 'be.loaded':
|
|
181
|
+
case 'shouldBeLoaded':
|
|
182
|
+
case 'be.pageLoaded':
|
|
183
|
+
case 'shouldBePageLoaded':
|
|
184
|
+
return `Expect page to be fully loaded`;
|
|
185
|
+
case 'have.noConsoleErrors':
|
|
186
|
+
case 'shouldHaveNoConsoleErrors':
|
|
187
|
+
return `Expect browser console to have no errors`;
|
|
188
|
+
default:
|
|
189
|
+
return `Expect ${el} to match ${assertionKey}`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ExecutionSummary {
|
|
2
|
+
totalSuites: number;
|
|
3
|
+
totalTests: number;
|
|
4
|
+
passedCount: number;
|
|
5
|
+
failedCount: number;
|
|
6
|
+
durationMs: number;
|
|
7
|
+
reportLocation: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Renders the final execution summary box.
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderSummaryView(summary: ExecutionSummary): void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
/**
|
|
3
|
+
* Renders the final execution summary box.
|
|
4
|
+
*/
|
|
5
|
+
export function renderSummaryView(summary) {
|
|
6
|
+
const { totalSuites, totalTests, passedCount, failedCount, durationMs, reportLocation } = summary;
|
|
7
|
+
const durationSec = (durationMs / 1000).toFixed(2);
|
|
8
|
+
console.log();
|
|
9
|
+
console.log(chalk.bold('TestSpectra Execution Summary'));
|
|
10
|
+
console.log(chalk.gray('────────────────────────────────────────'));
|
|
11
|
+
console.log(`Suites: ${chalk.bold(totalSuites)}`);
|
|
12
|
+
console.log(`Tests: ${chalk.bold(totalTests)} total, ` +
|
|
13
|
+
`${passedCount > 0 ? chalk.green.bold(`${passedCount} passed`) : '0 passed'}, ` +
|
|
14
|
+
`${failedCount > 0 ? chalk.red.bold(`${failedCount} failed`) : '0 failed'}`);
|
|
15
|
+
console.log(`Duration: ${durationSec}s`);
|
|
16
|
+
console.log(`Report: ${chalk.cyan(reportLocation)}`);
|
|
17
|
+
console.log(chalk.gray('────────────────────────────────────────'));
|
|
18
|
+
console.log();
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare class TerminalStream {
|
|
2
|
+
private spinner;
|
|
3
|
+
private currentTestTitle;
|
|
4
|
+
private stepCount;
|
|
5
|
+
constructor();
|
|
6
|
+
/**
|
|
7
|
+
* Starts a new test case, logging the pending bullet and test title.
|
|
8
|
+
*/
|
|
9
|
+
startTest(title: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Begins a new step, rendering an active spinner on the terminal with 2 spaces indentation.
|
|
12
|
+
*/
|
|
13
|
+
startStep(text: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* Marks the current step as passed, updating the spinner to a green checkmark
|
|
16
|
+
* and appending the duration in gray with 2 spaces indentation.
|
|
17
|
+
*/
|
|
18
|
+
passStep(text: string, durationMs: number): void;
|
|
19
|
+
/**
|
|
20
|
+
* Marks the current step as failed, updating the spinner to a red cross,
|
|
21
|
+
* coloring the text red, appending the duration, and optionally logging the error with 2 spaces indentation.
|
|
22
|
+
*/
|
|
23
|
+
failStep(text: string, durationMs: number, errorMsg?: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Concludes the active test case as passed, overwriting the initial "●" line with "✓" and duration in-place.
|
|
26
|
+
*/
|
|
27
|
+
passTest(title: string, durationMs: number): void;
|
|
28
|
+
/**
|
|
29
|
+
* Concludes the active test case as failed, overwriting the initial "●" line with "✗" and duration in-place.
|
|
30
|
+
*/
|
|
31
|
+
failTest(title: string, durationMs: number): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
export class TerminalStream {
|
|
4
|
+
spinner;
|
|
5
|
+
currentTestTitle = '';
|
|
6
|
+
stepCount = 0;
|
|
7
|
+
constructor() {
|
|
8
|
+
this.spinner = ora({
|
|
9
|
+
spinner: 'dots',
|
|
10
|
+
color: 'cyan',
|
|
11
|
+
prefixText: ' ',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Starts a new test case, logging the pending bullet and test title.
|
|
16
|
+
*/
|
|
17
|
+
startTest(title) {
|
|
18
|
+
if (this.spinner.isSpinning) {
|
|
19
|
+
this.spinner.stop();
|
|
20
|
+
}
|
|
21
|
+
this.currentTestTitle = title;
|
|
22
|
+
this.stepCount = 0;
|
|
23
|
+
console.log(`${chalk.hex('#2dd4bf')('●')} ${chalk.white.bold(title)}`);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Begins a new step, rendering an active spinner on the terminal with 2 spaces indentation.
|
|
27
|
+
*/
|
|
28
|
+
startStep(text) {
|
|
29
|
+
if (this.spinner.isSpinning) {
|
|
30
|
+
this.spinner.stop();
|
|
31
|
+
}
|
|
32
|
+
this.spinner.start(text);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Marks the current step as passed, updating the spinner to a green checkmark
|
|
36
|
+
* and appending the duration in gray with 2 spaces indentation.
|
|
37
|
+
*/
|
|
38
|
+
passStep(text, durationMs) {
|
|
39
|
+
const durationStr = chalk.gray(`(${durationMs}ms)`);
|
|
40
|
+
this.spinner.succeed(`${text} ${durationStr}`);
|
|
41
|
+
this.stepCount++;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Marks the current step as failed, updating the spinner to a red cross,
|
|
45
|
+
* coloring the text red, appending the duration, and optionally logging the error with 2 spaces indentation.
|
|
46
|
+
*/
|
|
47
|
+
failStep(text, durationMs, errorMsg) {
|
|
48
|
+
const durationStr = chalk.gray(`(${durationMs}ms)`);
|
|
49
|
+
this.spinner.fail(`${chalk.red(text)} ${durationStr}`);
|
|
50
|
+
this.stepCount++;
|
|
51
|
+
if (errorMsg) {
|
|
52
|
+
console.error(chalk.red(` ↳ ${errorMsg}`));
|
|
53
|
+
this.stepCount++;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Concludes the active test case as passed, overwriting the initial "●" line with "✓" and duration in-place.
|
|
58
|
+
*/
|
|
59
|
+
passTest(title, durationMs) {
|
|
60
|
+
if (this.spinner.isSpinning) {
|
|
61
|
+
this.spinner.stop();
|
|
62
|
+
}
|
|
63
|
+
const testTitle = title || this.currentTestTitle;
|
|
64
|
+
const durationStr = chalk.gray(`(${durationMs}ms)`);
|
|
65
|
+
const completedHeader = `${chalk.hex('#34d399')('✓')} ${chalk.white.bold(testTitle)} ${durationStr}`;
|
|
66
|
+
const linesToMoveUp = this.stepCount + 1;
|
|
67
|
+
process.stdout.write(`\x1b[${linesToMoveUp}A\r\x1b[2K${completedHeader}\x1b[${linesToMoveUp}B\r\n`);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Concludes the active test case as failed, overwriting the initial "●" line with "✗" and duration in-place.
|
|
71
|
+
*/
|
|
72
|
+
failTest(title, durationMs) {
|
|
73
|
+
if (this.spinner.isSpinning) {
|
|
74
|
+
this.spinner.stop();
|
|
75
|
+
}
|
|
76
|
+
const testTitle = title || this.currentTestTitle;
|
|
77
|
+
const durationStr = chalk.gray(`(${durationMs}ms)`);
|
|
78
|
+
const completedHeader = `${chalk.hex('#f87171')('✗')} ${chalk.white.bold(testTitle)} ${durationStr}`;
|
|
79
|
+
const linesToMoveUp = this.stepCount + 1;
|
|
80
|
+
process.stdout.write(`\x1b[${linesToMoveUp}A\r\x1b[2K${completedHeader}\x1b[${linesToMoveUp}B\r\n`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export type ActionKey = 'click' | 'doubleClick' | 'rightClick' | 'type' | 'clear' | 'select' | 'hover' | 'dragDrop' | 'scrollIntoView' | 'longPress' | 'waitForElement' | 'navigate' | 'back' | 'forward' | 'refresh' | 'setViewport' | 'wait' | 'pressKey' | 'scroll' | 'swipe' | 'intercept' | 'clearCookies' | 'clearLocalStorage';
|
|
2
|
+
export type AssertionKey = 'be.visible' | 'not.be.visible' | 'exist' | 'not.exist' | 'be.clickable' | 'not.be.clickable' | 'be.enabled' | 'be.disabled' | 'be.checked' | 'not.be.checked' | 'be.selected' | 'not.be.selected' | 'be.focused' | 'not.be.focused' | 'have.text' | 'not.have.text' | 'contain.text' | 'not.contain.text' | 'have.value' | 'not.have.value' | 'contain.value' | 'not.contain.value' | 'have.attr' | 'not.have.attr' | 'have.class' | 'not.have.class' | 'have.css' | 'not.have.css' | 'have.length' | 'not.have.length' | 'have.length.greaterThan' | 'have.length.lessThan' | 'be.empty' | 'not.be.empty' | 'shouldHaveUrl' | 'shouldContainUrl' | 'shouldHaveTitle' | 'shouldContainTitle' | 'shouldBeLoaded' | 'shouldBePageLoaded' | 'shouldHaveNoConsoleErrors' | 'have.url' | 'contain.url' | 'have.title' | 'contain.title' | 'be.loaded' | 'be.pageLoaded' | 'have.noConsoleErrors';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/runner/bridge.d.ts
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import { ConfigData } from
|
|
2
|
-
import { Reporter, TestRunResult } from
|
|
1
|
+
import { ConfigData } from '../config/schema.js';
|
|
2
|
+
import { Reporter, TestRunResult } from './reporter.js';
|
|
3
3
|
export interface RunOptions {
|
|
4
4
|
baseDir: string;
|
|
5
5
|
appDataPath: string;
|
|
6
|
-
platform:
|
|
7
|
-
suite
|
|
8
|
-
testCases
|
|
6
|
+
platform: 'web' | 'android' | 'ios' | 'common' | 'mobile';
|
|
7
|
+
suite?: string;
|
|
8
|
+
testCases?: Array<{
|
|
9
9
|
id: string;
|
|
10
10
|
title: string;
|
|
11
11
|
executionOrder?: number;
|
|
12
12
|
}>;
|
|
13
13
|
config: ConfigData;
|
|
14
14
|
targetDevice?: string;
|
|
15
|
+
licenseKey?: string;
|
|
15
16
|
outputJsonPath?: string;
|
|
17
|
+
spec?: string;
|
|
18
|
+
concurrency?: number;
|
|
19
|
+
failedOnly?: boolean;
|
|
20
|
+
failedTarget?: string;
|
|
16
21
|
}
|
|
17
22
|
export declare class RustCoreBridge {
|
|
18
23
|
static resolveBinaryPath(): string;
|
package/dist/runner/bridge.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import { spawn } from
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
import { fileURLToPath } from
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
export class RustCoreBridge {
|
|
8
8
|
static resolveBinaryPath() {
|
|
9
9
|
// 1. Look for precompiled release/debug binary in workspace target
|
|
10
|
-
const workspaceRoot = path.resolve(__dirname,
|
|
10
|
+
const workspaceRoot = path.resolve(__dirname, '../../..');
|
|
11
|
+
const isWindows = process.platform === 'win32';
|
|
12
|
+
const binaryName = isWindows ? 'testspectra-runner.exe' : 'testspectra-runner';
|
|
11
13
|
const candidatePaths = [
|
|
12
|
-
path.join(workspaceRoot,
|
|
13
|
-
path.join(workspaceRoot,
|
|
14
|
-
path.join(workspaceRoot,
|
|
15
|
-
path.join(
|
|
16
|
-
path.join(
|
|
14
|
+
path.join(workspaceRoot, 'core/target/release', binaryName),
|
|
15
|
+
path.join(workspaceRoot, 'target/release', binaryName),
|
|
16
|
+
path.join(workspaceRoot, 'cli/bin', binaryName),
|
|
17
|
+
path.join(__dirname, '../../bin', binaryName),
|
|
18
|
+
path.join(workspaceRoot, 'core/cli/target/release', binaryName),
|
|
19
|
+
path.join(workspaceRoot, 'core/target/debug', binaryName),
|
|
20
|
+
path.join(workspaceRoot, 'target/debug', binaryName),
|
|
21
|
+
path.join(workspaceRoot, 'core/cli/target/debug', binaryName),
|
|
22
|
+
path.join(__dirname, '../../bin/testspectra-runner'),
|
|
17
23
|
];
|
|
18
24
|
for (const p of candidatePaths) {
|
|
19
25
|
if (fs.existsSync(p)) {
|
|
@@ -23,7 +29,8 @@ export class RustCoreBridge {
|
|
|
23
29
|
return `${p}.exe`;
|
|
24
30
|
}
|
|
25
31
|
}
|
|
26
|
-
|
|
32
|
+
throw new Error("[TestSpectra] Native core runner binary 'testspectra-runner' not found.\n" +
|
|
33
|
+
"Please build the project first using 'pnpm run build' or 'pnpm run build:core'.");
|
|
27
34
|
}
|
|
28
35
|
static async run(options, reporter) {
|
|
29
36
|
const binPath = this.resolveBinaryPath();
|
|
@@ -35,9 +42,15 @@ export class RustCoreBridge {
|
|
|
35
42
|
testCases: options.testCases,
|
|
36
43
|
config: options.config,
|
|
37
44
|
targetDevice: options.targetDevice,
|
|
45
|
+
licenseKey: options.licenseKey,
|
|
46
|
+
outputJsonPath: options.outputJsonPath,
|
|
47
|
+
spec: options.spec,
|
|
48
|
+
concurrency: options.concurrency,
|
|
49
|
+
failedOnly: options.failedOnly,
|
|
50
|
+
failedTarget: options.failedTarget,
|
|
38
51
|
});
|
|
39
|
-
const home = process.env.HOME || process.env.USERPROFILE ||
|
|
40
|
-
const globalDriversDir = path.join(home,
|
|
52
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
53
|
+
const globalDriversDir = path.join(home, '.testspectra', 'drivers');
|
|
41
54
|
if (!fs.existsSync(globalDriversDir)) {
|
|
42
55
|
try {
|
|
43
56
|
fs.mkdirSync(globalDriversDir, { recursive: true });
|
|
@@ -47,39 +60,37 @@ export class RustCoreBridge {
|
|
|
47
60
|
const runnerEnv = {
|
|
48
61
|
...process.env,
|
|
49
62
|
TEST_SPECTRA_DRIVER_CACHE: globalDriversDir,
|
|
50
|
-
RUST_LOG:
|
|
63
|
+
RUST_LOG: 'info',
|
|
51
64
|
};
|
|
52
65
|
return new Promise((resolve, reject) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
child = spawn(binPath, [payload], {
|
|
63
|
-
env: runnerEnv,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
let runStatus = "failed";
|
|
67
|
-
let runDuration = "0s";
|
|
68
|
-
child.stdout.on("data", (data) => {
|
|
69
|
-
const lines = data.toString().split("\n");
|
|
66
|
+
const child = spawn(binPath, [payload], {
|
|
67
|
+
env: runnerEnv,
|
|
68
|
+
});
|
|
69
|
+
let runStatus = 'failed';
|
|
70
|
+
let runDuration = '0s';
|
|
71
|
+
child.stdout.on('data', (data) => {
|
|
72
|
+
const lines = data.toString().split('\n');
|
|
70
73
|
for (const line of lines) {
|
|
71
74
|
if (!line.trim())
|
|
72
75
|
continue;
|
|
76
|
+
if (line.startsWith('[TESTSPECTRA_')) {
|
|
77
|
+
reporter.addLog({
|
|
78
|
+
timestamp: new Date().toLocaleTimeString(),
|
|
79
|
+
level: 'INFO',
|
|
80
|
+
message: line,
|
|
81
|
+
});
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
73
84
|
try {
|
|
74
85
|
const parsed = JSON.parse(line);
|
|
75
|
-
if (parsed.type ===
|
|
86
|
+
if (parsed.type === 'log') {
|
|
76
87
|
reporter.addLog({
|
|
77
88
|
timestamp: parsed.timestamp,
|
|
78
89
|
level: parsed.level,
|
|
79
90
|
message: parsed.message,
|
|
80
91
|
});
|
|
81
92
|
}
|
|
82
|
-
else if (parsed.type ===
|
|
93
|
+
else if (parsed.type === 'network') {
|
|
83
94
|
reporter.addNetwork({
|
|
84
95
|
requestId: parsed.requestId,
|
|
85
96
|
url: parsed.url,
|
|
@@ -87,14 +98,14 @@ export class RustCoreBridge {
|
|
|
87
98
|
status: parsed.status,
|
|
88
99
|
});
|
|
89
100
|
}
|
|
90
|
-
else if (parsed.type ===
|
|
101
|
+
else if (parsed.type === 'complete') {
|
|
91
102
|
runStatus = parsed.status;
|
|
92
103
|
runDuration = parsed.duration;
|
|
93
104
|
}
|
|
94
|
-
else if (parsed.type ===
|
|
105
|
+
else if (parsed.type === 'error') {
|
|
95
106
|
reporter.addLog({
|
|
96
107
|
timestamp: new Date().toLocaleTimeString(),
|
|
97
|
-
level:
|
|
108
|
+
level: 'ERROR',
|
|
98
109
|
message: parsed.message,
|
|
99
110
|
});
|
|
100
111
|
}
|
|
@@ -105,29 +116,43 @@ export class RustCoreBridge {
|
|
|
105
116
|
}
|
|
106
117
|
}
|
|
107
118
|
});
|
|
108
|
-
child.stderr.on(
|
|
119
|
+
child.stderr.on('data', (data) => {
|
|
109
120
|
const str = data.toString();
|
|
110
121
|
// Ignore normal compilation progress
|
|
111
|
-
if (!str.includes(
|
|
122
|
+
if (!str.includes('Compiling') && !str.includes('Checking') && !str.includes('Finished')) {
|
|
112
123
|
reporter.addLog({
|
|
113
124
|
timestamp: new Date().toLocaleTimeString(),
|
|
114
|
-
level:
|
|
125
|
+
level: 'DEBUG',
|
|
115
126
|
message: str.trim(),
|
|
116
127
|
});
|
|
117
128
|
}
|
|
118
129
|
});
|
|
119
|
-
child.on(
|
|
120
|
-
|
|
121
|
-
if (options.outputJsonPath) {
|
|
130
|
+
child.on('close', (code) => {
|
|
131
|
+
let result = reporter.generateResult(code === 0 ? 'passed' : runStatus, runDuration);
|
|
132
|
+
if (options.outputJsonPath && fs.existsSync(options.outputJsonPath)) {
|
|
133
|
+
try {
|
|
134
|
+
const raw = fs.readFileSync(options.outputJsonPath, 'utf-8');
|
|
135
|
+
const parsed = JSON.parse(raw);
|
|
136
|
+
if (parsed.version && parsed.summary) {
|
|
137
|
+
result.status = parsed.status || result.status;
|
|
138
|
+
result.duration = parsed.duration || result.duration;
|
|
139
|
+
result.passedCount = parsed.summary.passed;
|
|
140
|
+
result.failedCount = parsed.summary.failed;
|
|
141
|
+
return resolve(result);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch { }
|
|
145
|
+
}
|
|
146
|
+
else if (options.outputJsonPath) {
|
|
122
147
|
const outDir = path.dirname(options.outputJsonPath);
|
|
123
148
|
if (!fs.existsSync(outDir)) {
|
|
124
149
|
fs.mkdirSync(outDir, { recursive: true });
|
|
125
150
|
}
|
|
126
|
-
fs.writeFileSync(options.outputJsonPath, JSON.stringify(result, null, 2),
|
|
151
|
+
fs.writeFileSync(options.outputJsonPath, JSON.stringify(result, null, 2), 'utf-8');
|
|
127
152
|
}
|
|
128
153
|
resolve(result);
|
|
129
154
|
});
|
|
130
|
-
child.on(
|
|
155
|
+
child.on('error', (err) => {
|
|
131
156
|
reject(err);
|
|
132
157
|
});
|
|
133
158
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface TestLog {
|
|
2
2
|
timestamp: string;
|
|
3
|
-
level:
|
|
3
|
+
level: 'INFO' | 'SUCCESS' | 'WARNING' | 'ERROR' | 'DEBUG' | string;
|
|
4
4
|
message: string;
|
|
5
5
|
}
|
|
6
6
|
export interface NetworkResource {
|
|
@@ -10,14 +10,42 @@ export interface NetworkResource {
|
|
|
10
10
|
status: number;
|
|
11
11
|
}
|
|
12
12
|
export interface TestRunResult {
|
|
13
|
-
status:
|
|
13
|
+
status: 'passed' | 'failed' | 'error' | string;
|
|
14
14
|
duration: string;
|
|
15
|
+
passedCount: number;
|
|
16
|
+
failedCount: number;
|
|
15
17
|
logs: TestLog[];
|
|
16
18
|
networkResources?: NetworkResource[];
|
|
17
19
|
}
|
|
20
|
+
export interface PendingStep {
|
|
21
|
+
text: string;
|
|
22
|
+
duration: number;
|
|
23
|
+
passed: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface PendingTestCase {
|
|
26
|
+
title: string;
|
|
27
|
+
workerId?: number;
|
|
28
|
+
steps: PendingStep[];
|
|
29
|
+
currentStep?: string;
|
|
30
|
+
errorMsg?: string;
|
|
31
|
+
startTime: number;
|
|
32
|
+
}
|
|
18
33
|
export declare class Reporter {
|
|
19
34
|
private logs;
|
|
20
35
|
private networkEvents;
|
|
36
|
+
private pendingTests;
|
|
37
|
+
private workerToTest;
|
|
38
|
+
private passedCount;
|
|
39
|
+
private failedCount;
|
|
40
|
+
private isInteractive;
|
|
41
|
+
private lastRenderedLines;
|
|
42
|
+
private spinnerFrames;
|
|
43
|
+
private spinnerIndex;
|
|
44
|
+
private spinnerTimer;
|
|
45
|
+
constructor();
|
|
46
|
+
private clearActiveSlots;
|
|
47
|
+
private renderActiveSlots;
|
|
48
|
+
private resolveTestCase;
|
|
21
49
|
addLog(log: TestLog): void;
|
|
22
50
|
addNetwork(res: NetworkResource): void;
|
|
23
51
|
getLogs(): TestLog[];
|