@testspectra/cli 1.0.70 → 1.1.0-rc.0
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/spectra.js +1 -1
- package/bin/testspectra-runner +0 -0
- package/demo-app/assets/index-BGllUp7o.js +140 -0
- package/demo-app/assets/index-BhlW-eXG.css +1 -0
- package/demo-app/index.html +2 -2
- package/dist/.tsbuildinfo +1 -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 +422 -0
- package/dist/commands/__tests__/run.test.d.ts +1 -0
- package/dist/commands/__tests__/run.test.js +277 -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/demo.js +21 -11
- 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/lint.d.ts +5 -0
- package/dist/commands/lint.js +55 -0
- package/dist/commands/refactor.d.ts +9 -0
- package/dist/commands/refactor.js +172 -0
- package/dist/commands/run.d.ts +23 -2
- package/dist/commands/run.js +321 -111
- package/dist/commands/skills.d.ts +9 -0
- package/dist/commands/skills.js +28 -0
- 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 +46 -43
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +40 -25
- package/dist/config/schema.d.ts +117 -54
- package/dist/config/schema.js +41 -35
- 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 +384 -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 +151 -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/linter/__tests__/anonymous-export.test.d.ts +1 -0
- package/dist/linter/__tests__/anonymous-export.test.js +184 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
- package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
- package/dist/linter/__tests__/spec-schema.test.js +369 -0
- package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
- package/dist/linter/__tests__/step-parity.test.js +372 -0
- package/dist/linter/discovery.d.ts +30 -0
- package/dist/linter/discovery.js +249 -0
- package/dist/linter/extractor.d.ts +66 -0
- package/dist/linter/extractor.js +235 -0
- package/dist/linter/index.d.ts +7 -0
- package/dist/linter/index.js +7 -0
- package/dist/linter/parser.d.ts +24 -0
- package/dist/linter/parser.js +219 -0
- package/dist/linter/schemas/spec.schema.d.ts +83 -0
- package/dist/linter/schemas/spec.schema.js +150 -0
- package/dist/linter/schemas/suite.schema.d.ts +59 -0
- package/dist/linter/schemas/suite.schema.js +83 -0
- package/dist/linter/spec-linter.d.ts +23 -0
- package/dist/linter/spec-linter.js +595 -0
- package/dist/linter/types.d.ts +28 -0
- package/dist/linter/types.js +1 -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 +873 -0
- package/dist/refactor/index.d.ts +1 -0
- package/dist/refactor/index.js +1 -0
- package/dist/refactor/refactor-engine.d.ts +50 -0
- package/dist/refactor/refactor-engine.js +587 -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 +101 -45
- package/dist/runner/reporter.d.ts +32 -2
- package/dist/runner/reporter.js +423 -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/__tests__/api-server.test.d.ts +1 -0
- package/dist/utils/__tests__/api-server.test.js +62 -0
- package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
- package/dist/utils/__tests__/demo-server.test.js +35 -0
- package/dist/utils/api-server.d.ts +19 -0
- package/dist/utils/api-server.js +134 -0
- package/dist/utils/demo-server.d.ts +5 -0
- package/dist/utils/demo-server.js +46 -36
- package/package.json +24 -13
- package/templates/default/CLAUDE.md +12 -5
- package/templates/default/fixtures/apiData.json +24 -0
- package/templates/default/package.json +12 -3
- package/templates/default/page-objects/MatchersPage/mobile.ts +53 -17
- package/templates/default/page-objects/MatchersPage/web.ts +17 -2
- package/templates/default/page-objects/NavigationPage/mobile.ts +11 -2
- package/templates/default/page-objects/NavigationPage/web.ts +1 -1
- package/templates/default/page-objects/PlaygroundPage/mobile.ts +28 -18
- package/templates/default/page-objects/PlaygroundPage/web.ts +4 -2
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -0
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +33 -0
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
- package/templates/default/specs/ApiInterception/suite.md +10 -0
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
- package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
- package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
- package/templates/default/specs/ApiSeeding/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/spec.md +5 -0
- 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/common.test.ts +26 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +7 -0
- 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/spec.md +5 -0
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
- package/templates/default/specs/DeepLink/suite.md +9 -0
- 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/spec.md +5 -0
- 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/spec.md +7 -0
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
- package/templates/default/specs/ElementMatchers/suite.md +2 -1
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
- package/templates/default/specs/PermissionRationale/suite.md +9 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +6 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
- package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +6 -0
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
- package/templates/default/spectra.config.ts +21 -15
- package/templates/default/support/actions/fillCredentials/mobile.action.ts +3 -3
- package/templates/default/support/actions/fillCredentials/web.action.ts +1 -1
- package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +3 -1
- package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +3 -3
- package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +4 -2
- package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +4 -4
- package/templates/default/tsconfig.json +3 -1
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db +0 -0
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm +0 -0
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
- package/templates/nx/.nx/workspace-data/d/daemon.log +4306 -0
- package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
- package/templates/nx/.nx/workspace-data/file-map.json +452 -0
- package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
- package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
- package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
- package/templates/nx/.nx/workspace-data/project-graph.json +14389 -0
- package/templates/nx/CLAUDE.md +2 -2
- package/templates/nx/package.json +6 -4
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -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 +24 -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/mobile.test.ts +1 -1
- 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/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +15 -5
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +1 -1
- 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/mobile.test.ts +1 -1
- 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/mobile.test.ts +1 -1
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +1 -1
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
- 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 +13 -1
- package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +85 -17
- package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +1 -1
- package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +1 -1
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +1 -1
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +4 -2
- package/templates/nx/shared/testing/project.json +19 -0
- package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +1 -1
- package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +1 -1
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +1 -1
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +3 -3
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +2 -2
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +4 -4
- package/templates/nx/spectra.config.ts +19 -13
- 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/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
- 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,134 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
/** Port the self-hosted demo API server listens on (separate from the demo web app's 5173). */
|
|
3
|
+
export const DEMO_API_PORT = 5200;
|
|
4
|
+
// Self-hosted dummy REST data for the demo app's API sections — keeps the ApiInterception and
|
|
5
|
+
// ApiSeeding suites fully offline/deterministic instead of depending on an external service
|
|
6
|
+
// (jsonplaceholder.typicode.com) whose reachability and latency were themselves a source of
|
|
7
|
+
// flakiness. Mocked requests never reach these handlers (CDP/proxy intercepts them first); they
|
|
8
|
+
// back the genuine passthrough cases (real network call recording, seed/cleanup hooks).
|
|
9
|
+
const API_POSTS = [
|
|
10
|
+
{
|
|
11
|
+
userId: 1,
|
|
12
|
+
id: 1,
|
|
13
|
+
title: 'TestSpectra demo post one',
|
|
14
|
+
body: 'Self-hosted demo data served by the built-in TestSpectra API server.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
userId: 1,
|
|
18
|
+
id: 2,
|
|
19
|
+
title: 'TestSpectra demo post two',
|
|
20
|
+
body: 'No external network required — deterministic for CI and offline runs.',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
userId: 2,
|
|
24
|
+
id: 3,
|
|
25
|
+
title: 'TestSpectra demo post three',
|
|
26
|
+
body: 'Intercept it with Spectra.intercept() or let it pass through untouched.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
userId: 2,
|
|
30
|
+
id: 4,
|
|
31
|
+
title: 'TestSpectra demo post four',
|
|
32
|
+
body: 'Served independently of the demo web app so either can run without the other.',
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
const API_USERS = [
|
|
36
|
+
{ id: 1, name: 'Ada Lovelace', username: 'ada', email: 'ada@testspectra.dev' },
|
|
37
|
+
{ id: 2, name: 'Alan Turing', username: 'alan', email: 'alan@testspectra.dev' },
|
|
38
|
+
{ id: 3, name: 'Grace Hopper', username: 'grace', email: 'grace@testspectra.dev' },
|
|
39
|
+
];
|
|
40
|
+
const API_TODOS = [{ userId: 1, id: 1, title: 'Verify the self-hosted demo API responds', completed: false }];
|
|
41
|
+
/**
|
|
42
|
+
* Standalone self-hosted REST API server for the demo apps, kept separate from `DemoServer` (the
|
|
43
|
+
* static web app) so the API can run independently and either server can be swapped for a real
|
|
44
|
+
* backend in a project. Mirrors the small subset of jsonplaceholder.typicode.com shapes the demo's
|
|
45
|
+
* API sections exercise.
|
|
46
|
+
*/
|
|
47
|
+
export class DemoApiServer {
|
|
48
|
+
server = null;
|
|
49
|
+
port;
|
|
50
|
+
constructor(port = DEMO_API_PORT) {
|
|
51
|
+
this.port = port;
|
|
52
|
+
}
|
|
53
|
+
async start() {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
this.server = http.createServer((req, res) => {
|
|
56
|
+
this.handleApi(req, res, req.url ? req.url.split('?')[0] : '/');
|
|
57
|
+
});
|
|
58
|
+
this.server.on('error', (err) => {
|
|
59
|
+
if (err.code === 'EADDRINUSE') {
|
|
60
|
+
// Port already in use (e.g. user already running the API server)
|
|
61
|
+
resolve({ url: `http://localhost:${this.port}`, port: this.port });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
reject(err);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
this.server.listen(this.port, '0.0.0.0', () => {
|
|
68
|
+
const addr = this.server?.address();
|
|
69
|
+
const actualPort = typeof addr === 'object' && addr !== null ? addr.port : this.port;
|
|
70
|
+
resolve({ url: `http://localhost:${actualPort}`, port: actualPort });
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
handleApi(req, res, pathname) {
|
|
75
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
76
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
77
|
+
res.setHeader('Access-Control-Allow-Headers', '*');
|
|
78
|
+
if (req.method === 'OPTIONS') {
|
|
79
|
+
res.writeHead(204);
|
|
80
|
+
res.end();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const sendJson = (status, payload) => {
|
|
84
|
+
// no-store: the demo app fetches these without a cache-buster, so a cached body would be
|
|
85
|
+
// served for later, differently-mocked requests to the same URL instead of reaching the
|
|
86
|
+
// mock proxy.
|
|
87
|
+
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
88
|
+
res.end(JSON.stringify(payload));
|
|
89
|
+
};
|
|
90
|
+
if (pathname === '/api/posts' && req.method === 'GET') {
|
|
91
|
+
sendJson(200, API_POSTS);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (pathname === '/api/users' && req.method === 'GET') {
|
|
95
|
+
sendJson(200, API_USERS);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (pathname === '/api/todos/1' && req.method === 'GET') {
|
|
99
|
+
sendJson(200, API_TODOS[0]);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (pathname === '/api/posts' && req.method === 'POST') {
|
|
103
|
+
const chunks = [];
|
|
104
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
105
|
+
req.on('end', () => {
|
|
106
|
+
let payload = {};
|
|
107
|
+
try {
|
|
108
|
+
payload = JSON.parse(Buffer.concat(chunks).toString('utf-8') || '{}');
|
|
109
|
+
}
|
|
110
|
+
catch { }
|
|
111
|
+
sendJson(201, { ...payload, id: Date.now() });
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (pathname.startsWith('/api/posts/') && req.method === 'DELETE') {
|
|
116
|
+
sendJson(200, {});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
sendJson(404, { error: 'Not Found' });
|
|
120
|
+
}
|
|
121
|
+
async stop() {
|
|
122
|
+
if (this.server) {
|
|
123
|
+
if (typeof this.server.closeAllConnections === 'function') {
|
|
124
|
+
this.server.closeAllConnections();
|
|
125
|
+
}
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
this.server?.close(() => {
|
|
128
|
+
this.server = null;
|
|
129
|
+
resolve();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static file server for the demo web app bundle. Serves ONLY the web app — the self-hosted demo
|
|
3
|
+
* REST API lives in a separate `DemoApiServer` (`cli/src/utils/api-server.ts`, port 5200) so the
|
|
4
|
+
* two can run and be swapped independently.
|
|
5
|
+
*/
|
|
1
6
|
export declare class DemoServer {
|
|
2
7
|
private server;
|
|
3
8
|
private port;
|
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
import http from
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
import { fileURLToPath } from
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
5
|
const MIME_TYPES = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
'.html': 'text/html',
|
|
7
|
+
'.js': 'application/javascript',
|
|
8
|
+
'.css': 'text/css',
|
|
9
|
+
'.json': 'application/json',
|
|
10
|
+
'.png': 'image/png',
|
|
11
|
+
'.jpg': 'image/jpeg',
|
|
12
|
+
'.gif': 'image/gif',
|
|
13
|
+
'.svg': 'image/svg+xml',
|
|
14
|
+
'.ico': 'image/x-icon',
|
|
15
|
+
'.woff': 'font/woff',
|
|
16
|
+
'.woff2': 'font/woff2',
|
|
17
|
+
'.ttf': 'font/ttf',
|
|
18
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* Static file server for the demo web app bundle. Serves ONLY the web app — the self-hosted demo
|
|
21
|
+
* REST API lives in a separate `DemoApiServer` (`cli/src/utils/api-server.ts`, port 5200) so the
|
|
22
|
+
* two can run and be swapped independently.
|
|
23
|
+
*/
|
|
19
24
|
export class DemoServer {
|
|
20
25
|
server = null;
|
|
21
26
|
port;
|
|
@@ -26,14 +31,14 @@ export class DemoServer {
|
|
|
26
31
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
32
|
const __dirname = path.dirname(__filename);
|
|
28
33
|
const candidates = [
|
|
29
|
-
path.resolve(__dirname,
|
|
30
|
-
path.resolve(__dirname,
|
|
31
|
-
path.resolve(__dirname,
|
|
32
|
-
path.resolve(__dirname,
|
|
33
|
-
path.resolve(__dirname,
|
|
34
|
+
path.resolve(__dirname, '../../demo-app'),
|
|
35
|
+
path.resolve(__dirname, '../demo-app'),
|
|
36
|
+
path.resolve(__dirname, '../../../demo-app'),
|
|
37
|
+
path.resolve(__dirname, '../../../apps/demo-web-app/dist'),
|
|
38
|
+
path.resolve(__dirname, '../../../../apps/demo-web-app/dist'),
|
|
34
39
|
];
|
|
35
40
|
for (const cand of candidates) {
|
|
36
|
-
if (fs.existsSync(cand) && fs.existsSync(path.join(cand,
|
|
41
|
+
if (fs.existsSync(cand) && fs.existsSync(path.join(cand, 'index.html'))) {
|
|
37
42
|
return cand;
|
|
38
43
|
}
|
|
39
44
|
}
|
|
@@ -46,32 +51,32 @@ export class DemoServer {
|
|
|
46
51
|
}
|
|
47
52
|
return new Promise((resolve, reject) => {
|
|
48
53
|
this.server = http.createServer((req, res) => {
|
|
49
|
-
const reqUrl = req.url ? req.url.split(
|
|
50
|
-
let safePath = path.normalize(reqUrl).replace(/^(\.\.[
|
|
51
|
-
if (safePath ===
|
|
52
|
-
safePath =
|
|
54
|
+
const reqUrl = req.url ? req.url.split('?')[0] : '/';
|
|
55
|
+
let safePath = path.normalize(reqUrl).replace(/^(\.\.[/\\])+/, '');
|
|
56
|
+
if (safePath === '/' || safePath === '\\')
|
|
57
|
+
safePath = '/index.html';
|
|
53
58
|
let filePath = path.join(demoDir, safePath);
|
|
54
59
|
// Fallback to index.html for SPA routes
|
|
55
60
|
if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
|
|
56
|
-
filePath = path.join(demoDir,
|
|
61
|
+
filePath = path.join(demoDir, 'index.html');
|
|
57
62
|
}
|
|
58
63
|
const ext = path.extname(filePath).toLowerCase();
|
|
59
|
-
const contentType = MIME_TYPES[ext] ||
|
|
64
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
60
65
|
try {
|
|
61
66
|
const content = fs.readFileSync(filePath);
|
|
62
67
|
res.writeHead(200, {
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
'Content-Type': contentType,
|
|
69
|
+
'Access-Control-Allow-Origin': '*',
|
|
65
70
|
});
|
|
66
71
|
res.end(content);
|
|
67
72
|
}
|
|
68
73
|
catch {
|
|
69
|
-
res.writeHead(404, {
|
|
70
|
-
res.end(
|
|
74
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
75
|
+
res.end('Not Found');
|
|
71
76
|
}
|
|
72
77
|
});
|
|
73
|
-
this.server.on(
|
|
74
|
-
if (err.code ===
|
|
78
|
+
this.server.on('error', (err) => {
|
|
79
|
+
if (err.code === 'EADDRINUSE') {
|
|
75
80
|
// Port already in use (e.g. user already running server)
|
|
76
81
|
resolve({ url: `http://localhost:${this.port}`, port: this.port });
|
|
77
82
|
}
|
|
@@ -79,13 +84,18 @@ export class DemoServer {
|
|
|
79
84
|
reject(err);
|
|
80
85
|
}
|
|
81
86
|
});
|
|
82
|
-
this.server.listen(this.port,
|
|
83
|
-
|
|
87
|
+
this.server.listen(this.port, '0.0.0.0', () => {
|
|
88
|
+
const addr = this.server?.address();
|
|
89
|
+
const actualPort = typeof addr === 'object' && addr !== null ? addr.port : this.port;
|
|
90
|
+
resolve({ url: `http://localhost:${actualPort}`, port: actualPort });
|
|
84
91
|
});
|
|
85
92
|
});
|
|
86
93
|
}
|
|
87
94
|
async stop() {
|
|
88
95
|
if (this.server) {
|
|
96
|
+
if (typeof this.server.closeAllConnections === 'function') {
|
|
97
|
+
this.server.closeAllConnections();
|
|
98
|
+
}
|
|
89
99
|
return new Promise((resolve) => {
|
|
90
100
|
this.server?.close(() => {
|
|
91
101
|
this.server = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testspectra/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.1.0-rc.0",
|
|
4
4
|
"description": "TestSpectra Zero-Config Cross-Platform Test Runner CLI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -17,8 +17,12 @@
|
|
|
17
17
|
"testspectra": "./bin/spectra.js"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
+
"build:rust": "node scripts/ensure-runner-binary.js",
|
|
21
|
+
"prebuild": "node scripts/ensure-runner-binary.js",
|
|
20
22
|
"build": "tsc && node scripts/copy-demo.js",
|
|
21
23
|
"watch": "tsc -w",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
22
26
|
"prepublishOnly": "npm run build"
|
|
23
27
|
},
|
|
24
28
|
"files": [
|
|
@@ -26,22 +30,29 @@
|
|
|
26
30
|
"bin",
|
|
27
31
|
"templates",
|
|
28
32
|
"demo-app",
|
|
29
|
-
"README.md"
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE.md"
|
|
30
35
|
],
|
|
31
36
|
"dependencies": {
|
|
32
|
-
"@clack/prompts": "
|
|
33
|
-
"@testspectra/matchers": "^1.0.
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
37
|
+
"@clack/prompts": "catalog:",
|
|
38
|
+
"@testspectra/matchers": "^1.1.0-rc.0",
|
|
39
|
+
"@testspectra/skills": "^1.1.0-rc.0",
|
|
40
|
+
"chalk": "catalog:",
|
|
41
|
+
"commander": "catalog:",
|
|
42
|
+
"dotenv": "catalog:",
|
|
43
|
+
"ora": "catalog:",
|
|
44
|
+
"typescript": "catalog:",
|
|
45
|
+
"yaml": "catalog:",
|
|
46
|
+
"zod": "catalog:"
|
|
39
47
|
},
|
|
40
48
|
"devDependencies": {
|
|
41
|
-
"@types/node": "
|
|
42
|
-
"
|
|
43
|
-
"
|
|
49
|
+
"@types/node": "catalog:",
|
|
50
|
+
"@wdio/globals": "catalog:",
|
|
51
|
+
"@wdio/mocha-framework": "catalog:",
|
|
52
|
+
"@wdio/types": "catalog:",
|
|
53
|
+
"esbuild": "catalog:",
|
|
54
|
+
"vitest": "catalog:"
|
|
44
55
|
},
|
|
45
56
|
"type": "module",
|
|
46
|
-
"license": "
|
|
57
|
+
"license": "SEE LICENSE IN LICENSE.md"
|
|
47
58
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
# CLAUDE.md - TestSpectra Framework Conventions
|
|
2
2
|
|
|
3
3
|
## Commands
|
|
4
|
-
- Run all Web tests: `pnpm spectra run -t web`
|
|
5
|
-
- Run
|
|
6
|
-
- Run
|
|
4
|
+
- Run all Web tests: `pnpm test:web` (or `spectra run -t web`)
|
|
5
|
+
- Run all Mobile tests: `pnpm test:mobile` (or `spectra run -t mobile`)
|
|
6
|
+
- Run Android tests: `pnpm test:android` (or `spectra run -t android`)
|
|
7
|
+
- Run iOS tests: `pnpm test:ios` (or `spectra run -t ios`)
|
|
8
|
+
- Run specific spec: `spectra run "specs/Playground/TC-0001-form-controls/mobile.test.ts" -t mobile`
|
|
7
9
|
- Type checking: `pnpm type-check` (or `tsc -b`)
|
|
8
|
-
- Preflight doctor: `pnpm spectra doctor`
|
|
9
|
-
-
|
|
10
|
+
- Preflight doctor: `pnpm doctor` (or `spectra doctor`)
|
|
11
|
+
- Sync ambient types: `pnpm sync-types`
|
|
12
|
+
|
|
13
|
+
## Element Selectors
|
|
14
|
+
- `~name` → **accessibility id** (Android `content-desc`, RN `accessibilityLabel`, web `aria-label`) — the portable default.
|
|
15
|
+
- `#name` → **native id** (Android `resource-id`, RN `testID`, web CSS `id`).
|
|
16
|
+
- Each prefix maps to exactly one attribute (no silent cross-matching). Prefer `~` for user-perceivable elements, `#` for stable test ids. See `docs/v2/cli/selectors.md`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mockPosts": [
|
|
3
|
+
{
|
|
4
|
+
"id": 1,
|
|
5
|
+
"title": "Mocked Post 1 - TestSpectra Matchers",
|
|
6
|
+
"body": "First mocked post body for verifying GET API interception in TestSpectra."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": 2,
|
|
10
|
+
"title": "Mocked Post 2 - Cross-Platform Automation",
|
|
11
|
+
"body": "Second mocked post body for verifying collection and item assertions."
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"mockNewPost": {
|
|
15
|
+
"title": "TestSpectra Automated Post",
|
|
16
|
+
"body": "End-to-End API mock verification with browser.intercept command."
|
|
17
|
+
},
|
|
18
|
+
"mockPostResponse": {
|
|
19
|
+
"id": 101,
|
|
20
|
+
"title": "TestSpectra Automated Post",
|
|
21
|
+
"body": "End-to-End API mock verification with browser.intercept command.",
|
|
22
|
+
"userId": 1
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -5,11 +5,20 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "spectra run",
|
|
8
|
-
"
|
|
8
|
+
"test:web": "spectra run -t web",
|
|
9
|
+
"test:mobile": "spectra run -t mobile",
|
|
10
|
+
"test:android": "spectra run -t android",
|
|
11
|
+
"test:ios": "spectra run -t ios",
|
|
12
|
+
"spectra": "spectra",
|
|
13
|
+
"doctor": "spectra doctor",
|
|
14
|
+
"watch": "spectra watch",
|
|
15
|
+
"type-check": "tsc -b",
|
|
16
|
+
"sync-types": "spectra sync-types",
|
|
17
|
+
"postinstall": "spectra sync-types"
|
|
9
18
|
},
|
|
10
19
|
"devDependencies": {
|
|
11
|
-
"@testspectra/cli": "^1.0.
|
|
12
|
-
"@testspectra/matchers": "^1.0.
|
|
20
|
+
"@testspectra/cli": "^1.1.0-rc.0",
|
|
21
|
+
"@testspectra/matchers": "^1.1.0-rc.0",
|
|
13
22
|
"@types/node": "^20.14.0",
|
|
14
23
|
"@wdio/cli": "^9.2.8",
|
|
15
24
|
"@wdio/local-runner": "^9.2.8",
|
|
@@ -1,21 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
get visibleElement() { return Spectra.get("~
|
|
3
|
-
get hiddenElement() { return Spectra.get("~
|
|
4
|
-
get existingElement() { return Spectra.get("~
|
|
5
|
-
get
|
|
6
|
-
get
|
|
7
|
-
get
|
|
8
|
-
get
|
|
9
|
-
get
|
|
10
|
-
get
|
|
11
|
-
get
|
|
12
|
-
get
|
|
13
|
-
get
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
class MatchersPage {
|
|
2
|
+
get visibleElement() { return Spectra.get("~visible-element"); }
|
|
3
|
+
get hiddenElement() { return Spectra.get("~hidden-element"); }
|
|
4
|
+
get existingElement() { return Spectra.get("~existing-element"); }
|
|
5
|
+
get removableElement() { return Spectra.get("~removable-element"); }
|
|
6
|
+
get submitButton() { return Spectra.get("~submit-button"); }
|
|
7
|
+
get disabledButton() { return Spectra.get("~disabled-button"); }
|
|
8
|
+
get checkbox() { return Spectra.get("~terms-checkbox"); }
|
|
9
|
+
get radio() { return Spectra.get("~mode-radio"); }
|
|
10
|
+
get emailInput() { return Spectra.get("~email-input"); }
|
|
11
|
+
get tokenInput() { return Spectra.get("~token-input"); }
|
|
12
|
+
get exactText() { return Spectra.get("~exact-text"); }
|
|
13
|
+
get partialText() { return Spectra.get("~partial-text"); }
|
|
14
|
+
// `#` = native id (RN testID → resource-id). Its accessibility label deliberately reads
|
|
15
|
+
// "active-tab is-active" (RN exposes no CSS classes, so the class is carried in the a11y label
|
|
16
|
+
// for `shouldHaveClass`), so it isn't addressable by the a11y-id form `~active-tab`.
|
|
17
|
+
get activeTab() { return Spectra.get("#active-tab"); }
|
|
18
|
+
get navLink() { return Spectra.get("~nav-link"); }
|
|
19
|
+
get styledElement() { return Spectra.get("~styled-element"); }
|
|
20
|
+
get focusInput() { return Spectra.get("~focus-input"); }
|
|
21
|
+
get productCards() { return Spectra.getAll("~product-item-1, ~product-item-2, ~product-item-3, ~product-item-4"); }
|
|
22
|
+
get emptyList() { return Spectra.getAll("~empty-list-item"); }
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
// API Demo elements
|
|
25
|
+
get fetchApiButton() { return Spectra.get("~fetch-api-btn"); }
|
|
26
|
+
get fetchStatusBadge() { return Spectra.get("~fetch-status-badge"); }
|
|
27
|
+
get fetchedItems() { return Spectra.getAll("~api-post-item"); }
|
|
28
|
+
get fetchLoadingIndicator() { return Spectra.get("~fetch-loading-indicator"); }
|
|
29
|
+
get fetchCustomUrlInput() { return Spectra.get("~fetch-custom-url"); }
|
|
30
|
+
get postTitleInput() { return Spectra.get("~post-title-input"); }
|
|
31
|
+
get postBodyInput() { return Spectra.get("~post-body-input"); }
|
|
32
|
+
get submitPostButton() { return Spectra.get("~submit-post-btn"); }
|
|
33
|
+
get postStatusBadge() { return Spectra.get("~post-status-badge"); }
|
|
34
|
+
get postSuccessBanner() { return Spectra.get("~post-success-banner"); }
|
|
35
|
+
get postResponseJson() { return Spectra.get("~post-response-json"); }
|
|
36
|
+
|
|
37
|
+
async open(section = "") {
|
|
38
|
+
try {
|
|
39
|
+
// A fresh app process (relaunched by the orchestrator's bootstrap before every suite) has to
|
|
40
|
+
// fetch, parse, and execute the RN JS bundle from Metro before anything is on screen — on a
|
|
41
|
+
// cold start this can take well past a normal action's poll budget. Wait for the home screen
|
|
42
|
+
// itself first so that timing doesn't silently swallow the sidebar click below and leave the
|
|
43
|
+
// real failure to surface confusingly on whatever element the test touches next.
|
|
44
|
+
await Spectra.get("~toggle-sidebar-btn").waitForElement(30000);
|
|
45
|
+
|
|
46
|
+
const isDrawerOpen = await Spectra.get("~close-sidebar-btn").isDisplayed().catch(() => false);
|
|
47
|
+
if (!isDrawerOpen) {
|
|
48
|
+
await Spectra.get("~toggle-sidebar-btn").click();
|
|
49
|
+
}
|
|
50
|
+
if (section) {
|
|
51
|
+
await Spectra.get(`~nav-item-${section}`).click();
|
|
52
|
+
}
|
|
53
|
+
} catch {}
|
|
54
|
+
}
|
|
19
55
|
}
|
|
20
56
|
|
|
21
57
|
export default new MatchersPage();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
class MatchersPage {
|
|
2
2
|
get visibleElement() { return Spectra.get("#demo-visible-el"); }
|
|
3
3
|
get hiddenElement() { return Spectra.get("#demo-hidden-el"); }
|
|
4
4
|
get existingElement() { return Spectra.get("#demo-existing-el"); }
|
|
@@ -12,11 +12,26 @@ export class MatchersPage {
|
|
|
12
12
|
get partialText() { return Spectra.get("#demo-partial-text"); }
|
|
13
13
|
get activeTab() { return Spectra.get("#demo-active-tab"); }
|
|
14
14
|
get navLink() { return Spectra.get("#demo-nav-link"); }
|
|
15
|
+
get styledElement() { return Spectra.get("#demo-styled-el"); }
|
|
16
|
+
get focusInput() { return Spectra.get("#demo-focus-input"); }
|
|
15
17
|
get productCards() { return Spectra.getAll(".demo-product-card"); }
|
|
16
18
|
get emptyList() { return Spectra.getAll("#demo-empty-list > *"); }
|
|
17
19
|
|
|
20
|
+
// API Interception & Mocking elements
|
|
21
|
+
get fetchApiButton() { return Spectra.get("#fetch-api-btn"); }
|
|
22
|
+
get fetchStatusBadge() { return Spectra.get("#fetch-status-badge"); }
|
|
23
|
+
get fetchedItems() { return Spectra.getAll(".api-fetched-item"); }
|
|
24
|
+
get fetchLoadingIndicator() { return Spectra.get("#fetch-loading-indicator"); }
|
|
25
|
+
get fetchCustomUrlInput() { return Spectra.get("#fetch-custom-url-input"); }
|
|
26
|
+
get postTitleInput() { return Spectra.get("#post-input-title"); }
|
|
27
|
+
get postBodyInput() { return Spectra.get("#post-input-body"); }
|
|
28
|
+
get submitPostButton() { return Spectra.get("#submit-post-btn"); }
|
|
29
|
+
get postStatusBadge() { return Spectra.get("#post-status-badge"); }
|
|
30
|
+
get postSuccessBanner() { return Spectra.get("#post-success-banner"); }
|
|
31
|
+
get postResponseJson() { return Spectra.get("#post-response-json"); }
|
|
32
|
+
|
|
18
33
|
async open(section = "") {
|
|
19
|
-
await
|
|
34
|
+
await Spectra.navigate("/" + (section ? "#" + section : ""));
|
|
20
35
|
}
|
|
21
36
|
}
|
|
22
37
|
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
get sidebarLinks() {
|
|
1
|
+
class NavigationPage {
|
|
2
|
+
get sidebarLinks() {
|
|
3
|
+
return Spectra.getAll(
|
|
4
|
+
"~nav-item-overview, ~nav-item-element-matchers, ~nav-item-visibility, ~nav-item-existence, ~nav-item-state, ~nav-item-value, ~nav-item-text, ~nav-item-class-attr, ~nav-item-browser-matchers, ~nav-item-url, ~nav-item-title, ~nav-item-collection-matchers, ~nav-item-length, ~nav-item-api-matchers, ~nav-item-api-fetch, ~nav-item-api-post, ~nav-item-playground"
|
|
5
|
+
);
|
|
6
|
+
}
|
|
7
|
+
get toggleSidebarButton() { return Spectra.get("~toggle-sidebar-btn"); }
|
|
8
|
+
get closeSidebarButton() { return Spectra.get("~close-sidebar-btn"); }
|
|
9
|
+
get overviewLink() { return Spectra.get("~nav-item-overview"); }
|
|
10
|
+
get visibilityLink() { return Spectra.get("~nav-item-visibility"); }
|
|
11
|
+
get playgroundLink() { return Spectra.get("~nav-item-playground"); }
|
|
3
12
|
}
|
|
4
13
|
|
|
5
14
|
export default new NavigationPage();
|
|
@@ -1,22 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
get visibleElement() { return Spectra.get("~
|
|
3
|
-
get hiddenElement() { return Spectra.get("~
|
|
4
|
-
get existingElement() { return Spectra.get("~
|
|
5
|
-
get removedElement() { return Spectra.get("~
|
|
6
|
-
get enabledButton() { return Spectra.get("~
|
|
7
|
-
get disabledButton() { return Spectra.get("~
|
|
8
|
-
get termsCheckbox() { return Spectra.get("~
|
|
9
|
-
get modeRadio() { return Spectra.get("~
|
|
10
|
-
get emailInput() { return Spectra.get("~
|
|
11
|
-
get tokenInput() { return Spectra.get("~
|
|
12
|
-
get exactText() { return Spectra.get("~
|
|
13
|
-
get partialText() { return Spectra.get("~
|
|
14
|
-
get activeTab() { return Spectra.get("~
|
|
15
|
-
get navLink() { return Spectra.get("~
|
|
16
|
-
get
|
|
17
|
-
get
|
|
1
|
+
class PlaygroundPage {
|
|
2
|
+
get visibleElement() { return Spectra.get("~playground-visible"); }
|
|
3
|
+
get hiddenElement() { return Spectra.get("~playground-hidden"); }
|
|
4
|
+
get existingElement() { return Spectra.get("~playground-existing"); }
|
|
5
|
+
get removedElement() { return Spectra.get("~playground-removable"); }
|
|
6
|
+
get enabledButton() { return Spectra.get("~enabled-button"); }
|
|
7
|
+
get disabledButton() { return Spectra.get("~pg-disabled-button"); }
|
|
8
|
+
get termsCheckbox() { return Spectra.get("~pg-terms-checkbox"); }
|
|
9
|
+
get modeRadio() { return Spectra.get("~pg-mode-radio"); }
|
|
10
|
+
get emailInput() { return Spectra.get("~pg-email-input"); }
|
|
11
|
+
get tokenInput() { return Spectra.get("~pg-token-input"); }
|
|
12
|
+
get exactText() { return Spectra.get("~exact-text-el"); }
|
|
13
|
+
get partialText() { return Spectra.get("~partial-text-el"); }
|
|
14
|
+
get activeTab() { return Spectra.get("~active-tab-el"); }
|
|
15
|
+
get navLink() { return Spectra.get("~nav-link-el"); }
|
|
16
|
+
get styledElement() { return Spectra.get("~playground-styled-element"); }
|
|
17
|
+
get focusInput() { return Spectra.get("~playground-focus-input"); }
|
|
18
|
+
get productItems() { return Spectra.getAll("~product-item-1, ~product-item-2, ~product-item-3, ~product-item-4"); }
|
|
19
|
+
get emptyList() { return Spectra.getAll("~empty-list-el-item"); }
|
|
18
20
|
|
|
19
|
-
async open() {
|
|
21
|
+
async open() {
|
|
22
|
+
try {
|
|
23
|
+
const isDrawerOpen = await Spectra.get("~close-sidebar-btn").isDisplayed().catch(() => false);
|
|
24
|
+
if (!isDrawerOpen) {
|
|
25
|
+
await Spectra.get("~toggle-sidebar-btn").click();
|
|
26
|
+
}
|
|
27
|
+
await Spectra.get("~nav-item-playground").click();
|
|
28
|
+
} catch {}
|
|
29
|
+
}
|
|
20
30
|
async fillCredentials(email = Fixture.playgroundData.defaultEmail, token = Fixture.playgroundData.defaultToken) {
|
|
21
31
|
await this.emailInput.type(email);
|
|
22
32
|
await this.tokenInput.type(token);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
class PlaygroundPage {
|
|
2
2
|
get visibleElement() { return Spectra.get("#demo-visible-el-pg"); }
|
|
3
3
|
get hiddenElement() { return Spectra.get("#demo-hidden-el-pg"); }
|
|
4
4
|
get existingElement() { return Spectra.get("#demo-existing-el-pg"); }
|
|
@@ -13,11 +13,13 @@ export class PlaygroundPage {
|
|
|
13
13
|
get partialText() { return Spectra.get("#demo-partial-text-pg"); }
|
|
14
14
|
get activeTab() { return Spectra.get("#demo-active-tab-pg"); }
|
|
15
15
|
get navLink() { return Spectra.get("#demo-nav-link-pg"); }
|
|
16
|
+
get styledElement() { return Spectra.get("#demo-styled-el-pg"); }
|
|
17
|
+
get focusInput() { return Spectra.get("#demo-focus-input-pg"); }
|
|
16
18
|
get productItems() { return Spectra.getAll(".demo-product-item"); }
|
|
17
19
|
get emptyList() { return Spectra.getAll("#demo-empty-list-pg > *"); }
|
|
18
20
|
|
|
19
21
|
async open() {
|
|
20
|
-
await
|
|
22
|
+
await Spectra.navigate("/#playground");
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
async fillCredentials(email = Fixture.playgroundData.defaultEmail, token = Fixture.playgroundData.defaultToken) {
|