@testspectra/cli 1.0.59 → 1.0.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -32
- package/bin/testspectra-runner +0 -0
- package/dist/commands/add.js +7 -13
- package/dist/commands/init.js +23 -23
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +22 -13
- package/dist/index.js +1 -0
- package/dist/server/demo.d.ts +8 -0
- package/dist/server/demo.js +104 -0
- package/dist/types/generator.d.ts +1 -1
- package/dist/types/generator.js +50 -15
- package/package.json +2 -2
- package/templates/default/.cursorrules +18 -0
- package/templates/default/CLAUDE.md +9 -0
- package/templates/default/fixtures/playgroundData.json +7 -0
- package/templates/default/global-hooks/after/mobile.hook.ts +3 -0
- package/templates/default/global-hooks/after/web.hook.ts +3 -0
- package/templates/default/global-hooks/before/mobile.hook.ts +3 -0
- package/templates/default/global-hooks/before/web.hook.ts +3 -0
- package/templates/default/package.json +5 -1
- package/templates/default/page-objects/MatchersPage/mobile.ts +21 -0
- package/templates/default/page-objects/MatchersPage/web.ts +23 -0
- package/templates/default/page-objects/NavigationPage/mobile.ts +5 -0
- package/templates/default/page-objects/NavigationPage/web.ts +8 -0
- package/templates/default/page-objects/PlaygroundPage/mobile.ts +26 -0
- package/templates/default/page-objects/PlaygroundPage/web.ts +29 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +12 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +5 -0
- package/templates/default/specs/BrowserContext/hooks/after/mobile.hook.ts +1 -0
- package/templates/default/specs/BrowserContext/hooks/after/web.hook.ts +1 -0
- package/templates/default/specs/BrowserContext/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/default/specs/BrowserContext/hooks/afterEach/web.hook.ts +1 -0
- package/templates/default/specs/BrowserContext/hooks/before/mobile.hook.ts +1 -0
- package/templates/default/specs/BrowserContext/hooks/before/web.hook.ts +1 -0
- package/templates/default/specs/BrowserContext/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/default/specs/BrowserContext/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/default/specs/BrowserContext/suite.md +9 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +12 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +7 -0
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -0
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +12 -0
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +6 -0
- package/templates/default/specs/ContentMatchers/hooks/after/mobile.hook.ts +1 -0
- package/templates/default/specs/ContentMatchers/hooks/after/web.hook.ts +1 -0
- package/templates/default/specs/ContentMatchers/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/default/specs/ContentMatchers/hooks/afterEach/web.hook.ts +1 -0
- package/templates/default/specs/ContentMatchers/hooks/before/mobile.hook.ts +1 -0
- package/templates/default/specs/ContentMatchers/hooks/before/web.hook.ts +1 -0
- package/templates/default/specs/ContentMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/default/specs/ContentMatchers/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/default/specs/ContentMatchers/suite.md +9 -0
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -0
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +12 -0
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +5 -0
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -0
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +12 -0
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +8 -0
- package/templates/default/specs/ElementMatchers/hooks/after/mobile.hook.ts +1 -0
- package/templates/default/specs/ElementMatchers/hooks/after/web.hook.ts +1 -0
- package/templates/default/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/default/specs/ElementMatchers/hooks/afterEach/web.hook.ts +1 -0
- package/templates/default/specs/ElementMatchers/hooks/before/mobile.hook.ts +1 -0
- package/templates/default/specs/ElementMatchers/hooks/before/web.hook.ts +1 -0
- package/templates/default/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/default/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/default/specs/ElementMatchers/suite.md +9 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +12 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +6 -0
- package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -0
- package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +12 -0
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -0
- package/templates/default/specs/Playground/hooks/after/mobile.hook.ts +1 -0
- package/templates/default/specs/Playground/hooks/after/web.hook.ts +1 -0
- package/templates/default/specs/Playground/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/default/specs/Playground/hooks/afterEach/web.hook.ts +1 -0
- package/templates/default/specs/Playground/hooks/before/mobile.hook.ts +1 -0
- package/templates/default/specs/Playground/hooks/before/web.hook.ts +1 -0
- package/templates/default/specs/Playground/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/default/specs/Playground/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/default/specs/Playground/suite.md +9 -0
- package/templates/default/spectra.config.ts +1 -1
- package/templates/default/support/actions/fillCredentials/mobile.action.ts +4 -0
- package/templates/default/support/actions/fillCredentials/web.action.ts +4 -0
- package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +3 -0
- package/templates/default/support/steps/togglePlaygroundStates/step.md +7 -0
- package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +5 -0
- package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +4 -0
- package/templates/default/support/steps/verifyPlaygroundState/step.md +7 -0
- package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +6 -0
- package/templates/default/tsconfig.spectra.android.json +7 -4
- package/templates/default/tsconfig.spectra.ios.json +7 -4
- package/templates/default/tsconfig.spectra.web.json +10 -6
- package/templates/nx/.cursorrules +18 -0
- package/templates/nx/CLAUDE.md +10 -0
- package/templates/nx/fixtures/playgroundData.json +7 -0
- package/templates/nx/global-hooks/after/mobile.hook.ts +3 -0
- package/templates/nx/global-hooks/after/web.hook.ts +3 -0
- package/templates/nx/global-hooks/before/mobile.hook.ts +3 -0
- package/templates/nx/global-hooks/before/web.hook.ts +3 -0
- package/templates/nx/package.json +1 -1
- package/templates/nx/{modules/auth → packages/matchers}/e2e/project.json +8 -5
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +5 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/after/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/after/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/afterEach/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/before/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/before/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/suite.md +9 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +7 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +6 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/after/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/after/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/afterEach/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/before/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/before/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/suite.md +9 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +5 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +8 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/after/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/after/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/afterEach/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/before/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/before/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/suite.md +9 -0
- package/templates/nx/packages/playground/e2e/project.json +37 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/spec.md +12 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +6 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/spec.md +12 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/hooks/after/mobile.hook.ts +1 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/hooks/after/web.hook.ts +1 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/hooks/afterEach/web.hook.ts +1 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/hooks/before/mobile.hook.ts +1 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/hooks/before/web.hook.ts +1 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/suite.md +9 -0
- package/templates/nx/page-objects/MatchersPage/mobile.ts +21 -0
- package/templates/nx/page-objects/MatchersPage/web.ts +23 -0
- package/templates/nx/page-objects/NavigationPage/mobile.ts +5 -0
- package/templates/nx/page-objects/NavigationPage/web.ts +8 -0
- package/templates/nx/page-objects/PlaygroundPage/mobile.ts +26 -0
- package/templates/nx/page-objects/PlaygroundPage/web.ts +29 -0
- package/templates/nx/shared/testing/fixtures/playgroundData.json +7 -0
- package/templates/nx/shared/testing/global-hooks/after/mobile.hook.ts +3 -0
- package/templates/nx/shared/testing/global-hooks/after/web.hook.ts +3 -0
- package/templates/nx/shared/testing/global-hooks/before/mobile.hook.ts +3 -0
- package/templates/nx/shared/testing/global-hooks/before/web.hook.ts +3 -0
- package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +21 -0
- package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +23 -0
- package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +5 -0
- package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +8 -0
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +26 -0
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +29 -0
- package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +4 -0
- package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +4 -0
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +3 -0
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/step.md +7 -0
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +5 -0
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +4 -0
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/step.md +7 -0
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +6 -0
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -0
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +12 -0
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +5 -0
- package/templates/nx/specs/BrowserContext/hooks/after/mobile.hook.ts +1 -0
- package/templates/nx/specs/BrowserContext/hooks/after/web.hook.ts +1 -0
- package/templates/nx/specs/BrowserContext/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/nx/specs/BrowserContext/hooks/afterEach/web.hook.ts +1 -0
- package/templates/nx/specs/BrowserContext/hooks/before/mobile.hook.ts +1 -0
- package/templates/nx/specs/BrowserContext/hooks/before/web.hook.ts +1 -0
- package/templates/nx/specs/BrowserContext/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/nx/specs/BrowserContext/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/nx/specs/BrowserContext/suite.md +9 -0
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -0
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +12 -0
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +7 -0
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -0
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +12 -0
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +6 -0
- package/templates/nx/specs/ContentMatchers/hooks/after/mobile.hook.ts +1 -0
- package/templates/nx/specs/ContentMatchers/hooks/after/web.hook.ts +1 -0
- package/templates/nx/specs/ContentMatchers/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/nx/specs/ContentMatchers/hooks/afterEach/web.hook.ts +1 -0
- package/templates/nx/specs/ContentMatchers/hooks/before/mobile.hook.ts +1 -0
- package/templates/nx/specs/ContentMatchers/hooks/before/web.hook.ts +1 -0
- package/templates/nx/specs/ContentMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/nx/specs/ContentMatchers/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/nx/specs/ContentMatchers/suite.md +9 -0
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -0
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +12 -0
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +5 -0
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -0
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +12 -0
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +8 -0
- package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +1 -0
- package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +1 -0
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +1 -0
- package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +1 -0
- package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +1 -0
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/nx/specs/ElementMatchers/suite.md +9 -0
- package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -0
- package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +12 -0
- package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +6 -0
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -0
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +12 -0
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -0
- package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +1 -0
- package/templates/nx/specs/Playground/hooks/after/web.hook.ts +1 -0
- package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +1 -0
- package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +1 -0
- package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +1 -0
- package/templates/nx/specs/Playground/hooks/before/web.hook.ts +1 -0
- package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +1 -0
- package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +1 -0
- package/templates/nx/specs/Playground/suite.md +9 -0
- package/templates/nx/spectra.config.ts +1 -1
- package/templates/nx/support/actions/fillCredentials/mobile.action.ts +4 -0
- package/templates/nx/support/actions/fillCredentials/web.action.ts +4 -0
- package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +3 -0
- package/templates/nx/support/steps/togglePlaygroundStates/step.md +7 -0
- package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +5 -0
- package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +4 -0
- package/templates/nx/support/steps/verifyPlaygroundState/step.md +7 -0
- package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +6 -0
- package/templates/default/actions/applyDiscount/mobile.action.ts +0 -6
- package/templates/default/actions/applyDiscount/web.action.ts +0 -6
- package/templates/default/actions/verifyOtp/mobile.action.ts +0 -6
- package/templates/default/actions/verifyOtp/web.action.ts +0 -6
- package/templates/default/fixtures/couponCode.json +0 -1
- package/templates/default/fixtures/searchQuery.json +0 -1
- package/templates/default/fixtures/userData.json +0 -1
- package/templates/default/global-hooks/before.web.hook.ts +0 -4
- package/templates/default/page-objects/LoginPage/mobile.ts +0 -32
- package/templates/default/page-objects/LoginPage/web.ts +0 -32
- package/templates/default/page-objects/ProductPage/mobile.ts +0 -39
- package/templates/default/page-objects/ProductPage/web.ts +0 -38
- package/templates/default/page-objects/SettingsPage/common.ts +0 -26
- package/templates/default/specs/Auth/TC-AUTH-01/android.test.ts +0 -6
- package/templates/default/specs/Auth/TC-AUTH-01/ios.test.ts +0 -6
- package/templates/default/specs/Auth/TC-AUTH-01/web.test.ts +0 -13
- package/templates/default/specs/Auth/TC-AUTH-02/android.test.ts +0 -5
- package/templates/default/specs/Auth/TC-AUTH-02/web.test.ts +0 -5
- package/templates/default/specs/Auth/hooks/before.android.hook.ts +0 -3
- package/templates/default/specs/Auth/hooks/before.ios.hook.ts +0 -3
- package/templates/default/specs/Auth/hooks/before.web.hook.ts +0 -3
- package/templates/default/specs/Product/TC-PROD-01/mobile.test.ts +0 -8
- package/templates/default/specs/Product/TC-PROD-01/web.test.ts +0 -8
- package/templates/default/specs/Product/TC-PROD-02/mobile.test.ts +0 -6
- package/templates/default/specs/Product/TC-PROD-02/web.test.ts +0 -6
- package/templates/default/specs/Product/hooks/before.web.hook.ts +0 -3
- package/templates/default/specs/Settings/TC-SET-01/common.test.ts +0 -8
- package/templates/default/steps/loginUser/mobile.step.ts +0 -4
- package/templates/default/steps/loginUser/web.step.ts +0 -4
- package/templates/default/steps/searchProduct/mobile.step.ts +0 -5
- package/templates/default/steps/searchProduct/web.step.ts +0 -5
- package/templates/nx/modules/auth/e2e/actions/verifyOtp/mobile.action.ts +0 -6
- package/templates/nx/modules/auth/e2e/actions/verifyOtp/web.action.ts +0 -6
- package/templates/nx/modules/auth/e2e/fixtures/userData.json +0 -1
- package/templates/nx/modules/auth/e2e/global-hooks/before.web.hook.ts +0 -4
- package/templates/nx/modules/auth/e2e/package.json +0 -19
- package/templates/nx/modules/auth/e2e/page-objects/LoginPage/mobile.ts +0 -32
- package/templates/nx/modules/auth/e2e/page-objects/LoginPage/web.ts +0 -32
- package/templates/nx/modules/auth/e2e/page-objects/SettingsPage/common.ts +0 -26
- package/templates/nx/modules/auth/e2e/specs/Auth/TC-AUTH-01/android.test.ts +0 -6
- package/templates/nx/modules/auth/e2e/specs/Auth/TC-AUTH-01/ios.test.ts +0 -6
- package/templates/nx/modules/auth/e2e/specs/Auth/TC-AUTH-01/web.test.ts +0 -19
- package/templates/nx/modules/auth/e2e/specs/Auth/TC-AUTH-02/android.test.ts +0 -5
- package/templates/nx/modules/auth/e2e/specs/Auth/TC-AUTH-02/web.test.ts +0 -5
- package/templates/nx/modules/auth/e2e/specs/Auth/hooks/before.android.hook.ts +0 -3
- package/templates/nx/modules/auth/e2e/specs/Auth/hooks/before.ios.hook.ts +0 -3
- package/templates/nx/modules/auth/e2e/specs/Auth/hooks/before.web.hook.ts +0 -3
- package/templates/nx/modules/auth/e2e/specs/Settings/TC-SET-01/common.test.ts +0 -8
- package/templates/nx/modules/auth/e2e/steps/loginUser/mobile.step.ts +0 -4
- package/templates/nx/modules/auth/e2e/steps/loginUser/web.step.ts +0 -4
- package/templates/nx/modules/auth/package.json +0 -7
- package/templates/nx/modules/auth/src/index.ts +0 -3
- package/templates/nx/modules/catalog/package.json +0 -7
- package/templates/nx/modules/catalog/src/index.ts +0 -6
- package/templates/nx/shared/testing/actions/dismissBanner/common.action.ts +0 -6
- package/templates/nx/shared/testing/fixtures/appConfig.json +0 -4
- package/templates/nx/shared/testing/package.json +0 -8
- package/templates/nx/shared/testing/page-objects/NavigationBar/common.ts +0 -13
- package/templates/nx/shared/testing/steps/loginAsAdmin/common.step.ts +0 -4
package/README.md
CHANGED
|
@@ -179,35 +179,36 @@ export default defineConfig({
|
|
|
179
179
|
├── tsconfig.android.json # Android platform TypeScript scope
|
|
180
180
|
├── tsconfig.ios.json # iOS platform TypeScript scope
|
|
181
181
|
├── page-objects/ # Page Object Model folders
|
|
182
|
-
│ └──
|
|
182
|
+
│ └── PlaygroundPage/
|
|
183
183
|
│ ├── web.ts
|
|
184
184
|
│ └── mobile.ts
|
|
185
|
-
├──
|
|
186
|
-
│
|
|
187
|
-
│
|
|
188
|
-
│
|
|
189
|
-
|
|
190
|
-
│ └──
|
|
191
|
-
│
|
|
192
|
-
│
|
|
185
|
+
├── support/
|
|
186
|
+
│ ├── actions/ # Reusable atomic actions
|
|
187
|
+
│ │ └── fillCredentials/
|
|
188
|
+
│ │ ├── web.action.ts
|
|
189
|
+
│ │ └── mobile.action.ts
|
|
190
|
+
│ └── steps/ # High-level business flows
|
|
191
|
+
│ └── verifyPlaygroundState/
|
|
192
|
+
│ ├── web.step.ts
|
|
193
|
+
│ └── mobile.step.ts
|
|
193
194
|
├── global-hooks/ # Global lifecycle hooks
|
|
194
|
-
│ └── before
|
|
195
|
+
│ └── before/
|
|
196
|
+
│ └── web.hook.ts
|
|
195
197
|
├── specs/ # Layered zero-import test cases
|
|
196
|
-
│ └──
|
|
197
|
-
│ └── TC-
|
|
198
|
+
│ └── Playground/
|
|
199
|
+
│ └── TC-0001-form-controls/
|
|
198
200
|
│ ├── web.test.ts
|
|
199
|
-
│
|
|
200
|
-
│ └── ios.test.ts
|
|
201
|
+
│ └── mobile.test.ts
|
|
201
202
|
├── fixtures/ # Platform-agnostic test data
|
|
202
|
-
│ └──
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
203
|
+
│ └── playgroundData.json
|
|
204
|
+
├── .testspectra/ # Auto-generated ambient typings & cache
|
|
205
|
+
│ └── types/
|
|
206
|
+
│ ├── fixtures.d.ts
|
|
207
|
+
│ ├── web.d.ts
|
|
208
|
+
│ ├── android.d.ts
|
|
209
|
+
│ ├── ios.d.ts
|
|
210
|
+
│ ├── mobile.d.ts
|
|
211
|
+
│ └── common.d.ts
|
|
211
212
|
```
|
|
212
213
|
|
|
213
214
|
---
|
|
@@ -230,19 +231,19 @@ TestSpectra features an enterprise-grade architecture for large monorepos: **"Ce
|
|
|
230
231
|
┌──────────────────────────────┴──────────────────────────────┐
|
|
231
232
|
▼ ▼
|
|
232
233
|
┌───────────────────────────────┐ ┌───────────────────────────────┐
|
|
233
|
-
│ shared/testing/ (Library) │ │
|
|
234
|
+
│ shared/testing/ (Library) │ │ packages/playground/e2e/ │
|
|
234
235
|
├───────────────────────────────┤ ├───────────────────────────────┤
|
|
235
236
|
│ - Shared Page Objects │ ◄─── Auto-Scanned Globals ──│ - Feature Specs (specs/) │
|
|
236
|
-
│ - Shared Steps (
|
|
237
|
-
│ - Shared Actions (
|
|
238
|
-
│ - Shared Fixtures (
|
|
237
|
+
│ - Shared Steps (verifyState) │ (100% Zero-Import!) │ - Feature Page Objects (POMs) │
|
|
238
|
+
│ - Shared Actions (fillCreds) │ │ - project.json (Nx Target) │
|
|
239
|
+
│ - Shared Fixtures (playData) │ │ - tsconfig.web/android/ios │
|
|
239
240
|
└───────────────────────────────┘ └───────────────────────────────┘
|
|
240
241
|
```
|
|
241
242
|
|
|
242
243
|
### Key Pillars of Monorepo Integration:
|
|
243
244
|
|
|
244
245
|
1. **Interactive Workspace Auto-Discovery (`spectra init`)**:
|
|
245
|
-
- Parses `pnpm-workspace.yaml` (e.g. `packages
|
|
246
|
+
- Parses `pnpm-workspace.yaml` (e.g. `packages/*`, `modules/**`).
|
|
246
247
|
- Scans sub-directories for existing `project.json` or `package.json`.
|
|
247
248
|
- Prompts the user with an interactive multi-select checkbox to choose which features will receive E2E testing.
|
|
248
249
|
- Prompts for customizable E2E folder names (e.g. `e2e`, `test/e2e`) and the shared testing library path (`shared/testing`).
|
|
@@ -253,18 +254,18 @@ TestSpectra features an enterprise-grade architecture for large monorepos: **"Ce
|
|
|
253
254
|
- Driver caches, binaries, and logs reside in `<root>/.testspectra/`, preventing repository clutter across individual feature folders.
|
|
254
255
|
|
|
255
256
|
3. **Global Zero-Import Consumption of Shared Library**:
|
|
256
|
-
- Entities placed inside `shared/testing/page-objects/`, `shared/testing/steps/`, `shared/testing/actions/`, and `shared/testing/fixtures/` are automatically aggregated into ambient declarations.
|
|
257
|
-
- Feature test specs consume shared Page Objects (`
|
|
257
|
+
- Entities placed inside `shared/testing/page-objects/`, `shared/testing/support/steps/`, `shared/testing/support/actions/`, and `shared/testing/fixtures/` are automatically aggregated into ambient declarations.
|
|
258
|
+
- Feature test specs consume shared Page Objects (`PlaygroundPage.open()`), steps (`Step.verifyPlaygroundState()`), actions (`Spectra.fillCredentials()`), and fixtures (`Fixture.playgroundData`) **without a single line of `import` statement**.
|
|
258
259
|
|
|
259
260
|
4. **Nx Target Execution & Affected Caching**:
|
|
260
261
|
- Each feature E2E folder includes an Nx `project.json`:
|
|
261
262
|
```json
|
|
262
263
|
{
|
|
263
|
-
"name": "
|
|
264
|
+
"name": "playground-e2e",
|
|
264
265
|
"targets": {
|
|
265
266
|
"e2e": {
|
|
266
267
|
"executor": "nx:run-commands",
|
|
267
|
-
"options": { "command": "spectra run", "cwd": "
|
|
268
|
+
"options": { "command": "spectra run", "cwd": "packages/playground/e2e" },
|
|
268
269
|
"configurations": {
|
|
269
270
|
"android": { "command": "spectra run --target android" },
|
|
270
271
|
"ios": { "command": "spectra run --target ios" },
|
package/bin/testspectra-runner
CHANGED
|
Binary file
|
package/dist/commands/add.js
CHANGED
|
@@ -130,7 +130,7 @@ export async function addCommand(moduleNameArg, options = {}) {
|
|
|
130
130
|
if (!fs.existsSync(templateDir)) {
|
|
131
131
|
templateDir = path.resolve(__dirname, "../../../templates/nx");
|
|
132
132
|
}
|
|
133
|
-
const sampleE2eSrc = path.join(templateDir, "
|
|
133
|
+
const sampleE2eSrc = path.join(templateDir, "packages/playground/e2e");
|
|
134
134
|
if (!fs.existsSync(sampleE2eSrc)) {
|
|
135
135
|
p.cancel(chalk.red(`Template directory not found at: ${sampleE2eSrc}`));
|
|
136
136
|
return;
|
|
@@ -151,18 +151,12 @@ export async function addCommand(moduleNameArg, options = {}) {
|
|
|
151
151
|
cur = rootDir;
|
|
152
152
|
while (cur !== path.dirname(cur)) {
|
|
153
153
|
if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml"))) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (wsContent.includes("cli")) {
|
|
157
|
-
isInternalWorkspace = true;
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
catch { }
|
|
154
|
+
isInternalWorkspace = true;
|
|
155
|
+
break;
|
|
162
156
|
}
|
|
163
157
|
cur = path.dirname(cur);
|
|
164
158
|
}
|
|
165
|
-
const cliDepVersion = isInternalWorkspace ? "workspace:*" : cliVersion;
|
|
159
|
+
const cliDepVersion = isInternalWorkspace ? "workspace:*" : (cliVersion.startsWith("^") ? cliVersion : `^${cliVersion}`);
|
|
166
160
|
function copyRecursive(src, dest, replacements = {}) {
|
|
167
161
|
const base = path.basename(src);
|
|
168
162
|
if (base === "node_modules" || base === "dist" || base === ".testspectra" || base === ".git") {
|
|
@@ -289,9 +283,9 @@ export async function addCommand(moduleNameArg, options = {}) {
|
|
|
289
283
|
const s = p.spinner();
|
|
290
284
|
s.start(`Scaffolding ${e2eProjectName} in ./${path.relative(rootDir, targetE2eDir)}...`);
|
|
291
285
|
copyRecursive(sampleE2eSrc, targetE2eDir, {
|
|
292
|
-
"
|
|
293
|
-
"
|
|
294
|
-
"scope:
|
|
286
|
+
"playground-e2e": e2eProjectName,
|
|
287
|
+
"packages/playground/e2e": path.relative(rootDir, targetE2eDir).replace(/\\/g, "/"),
|
|
288
|
+
"scope:playground": `scope:${featureName}`,
|
|
295
289
|
});
|
|
296
290
|
// Adjust project.json
|
|
297
291
|
const projJsonPath = path.join(targetE2eDir, "project.json");
|
package/dist/commands/init.js
CHANGED
|
@@ -256,19 +256,13 @@ export async function initCommand(options = {}) {
|
|
|
256
256
|
let cur = cwd;
|
|
257
257
|
while (cur !== path.dirname(cur)) {
|
|
258
258
|
if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml"))) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (wsContent.includes("cli")) {
|
|
262
|
-
isInternalWorkspace = true;
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
catch { }
|
|
259
|
+
isInternalWorkspace = true;
|
|
260
|
+
break;
|
|
267
261
|
}
|
|
268
262
|
cur = path.dirname(cur);
|
|
269
263
|
}
|
|
270
264
|
const projectName = path.basename(cwd);
|
|
271
|
-
const cliDepVersion = isInternalWorkspace ? "workspace:*" : cliVersion;
|
|
265
|
+
const cliDepVersion = isInternalWorkspace ? "workspace:*" : (cliVersion.startsWith("^") ? cliVersion : `^${cliVersion}`);
|
|
272
266
|
// Helper: Copy directory contents recursively
|
|
273
267
|
function copyRecursive(src, dest, replacements = {}) {
|
|
274
268
|
const base = path.basename(src);
|
|
@@ -344,7 +338,7 @@ export async function initCommand(options = {}) {
|
|
|
344
338
|
p.log.warn(chalk.yellow("No feature packages found in workspace. You can enter feature names to scaffold."));
|
|
345
339
|
const featureNamesInput = await p.text({
|
|
346
340
|
message: "Enter feature module names (comma-separated), or leave blank to skip:",
|
|
347
|
-
placeholder: "
|
|
341
|
+
placeholder: "playground, matchers, navigation",
|
|
348
342
|
defaultValue: "",
|
|
349
343
|
});
|
|
350
344
|
if (p.isCancel(featureNamesInput)) {
|
|
@@ -356,14 +350,14 @@ export async function initCommand(options = {}) {
|
|
|
356
350
|
// Build relPaths from feature names using a sensible default location
|
|
357
351
|
const featureBaseInput = await p.text({
|
|
358
352
|
message: "Enter base directory for feature modules:",
|
|
359
|
-
placeholder: "packages
|
|
360
|
-
defaultValue: "packages
|
|
353
|
+
placeholder: "packages",
|
|
354
|
+
defaultValue: "packages",
|
|
361
355
|
});
|
|
362
356
|
if (p.isCancel(featureBaseInput)) {
|
|
363
357
|
p.cancel("Setup cancelled.");
|
|
364
358
|
return;
|
|
365
359
|
}
|
|
366
|
-
const featureBase = featureBaseInput.trim() || "packages
|
|
360
|
+
const featureBase = featureBaseInput.trim() || "packages";
|
|
367
361
|
chosenProjects = raw.split(",").map((n) => `${featureBase}/${n.trim()}`).filter(Boolean);
|
|
368
362
|
}
|
|
369
363
|
}
|
|
@@ -442,6 +436,14 @@ export async function initCommand(options = {}) {
|
|
|
442
436
|
rootPkg.devDependencies["@testspectra/matchers"] = cliDepVersion;
|
|
443
437
|
if (!rootPkg.devDependencies["typescript"])
|
|
444
438
|
rootPkg.devDependencies["typescript"] = "^5.4.5";
|
|
439
|
+
if (!rootPkg.devDependencies["@wdio/cli"])
|
|
440
|
+
rootPkg.devDependencies["@wdio/cli"] = "^9.2.8";
|
|
441
|
+
if (!rootPkg.devDependencies["@wdio/local-runner"])
|
|
442
|
+
rootPkg.devDependencies["@wdio/local-runner"] = "^9.2.8";
|
|
443
|
+
if (!rootPkg.devDependencies["@wdio/spec-reporter"])
|
|
444
|
+
rootPkg.devDependencies["@wdio/spec-reporter"] = "^9.2.8";
|
|
445
|
+
if (!rootPkg.devDependencies["@wdio/devtools-service"])
|
|
446
|
+
rootPkg.devDependencies["@wdio/devtools-service"] = "^9.2.8";
|
|
445
447
|
if (!rootPkg.devDependencies["@wdio/globals"])
|
|
446
448
|
rootPkg.devDependencies["@wdio/globals"] = "^9.2.8";
|
|
447
449
|
if (!rootPkg.devDependencies["@wdio/mocha-framework"])
|
|
@@ -465,16 +467,16 @@ export async function initCommand(options = {}) {
|
|
|
465
467
|
copyRecursive(sharedTestingSrc, sharedTestingAbsPath);
|
|
466
468
|
}
|
|
467
469
|
// 4. Distribute E2E Folders to Selected Feature Modules
|
|
468
|
-
const sampleE2eSrc = path.join(templateDir, "
|
|
470
|
+
const sampleE2eSrc = path.join(templateDir, "packages/playground/e2e");
|
|
469
471
|
const featureE2eDirs = [];
|
|
470
472
|
for (const projRel of chosenProjects) {
|
|
471
473
|
const targetE2eDir = path.join(cwd, projRel, e2eFolderName);
|
|
472
474
|
const featureName = path.basename(projRel);
|
|
473
475
|
const e2eProjectName = `${featureName}-${e2eFolderName}`;
|
|
474
476
|
copyRecursive(sampleE2eSrc, targetE2eDir, {
|
|
475
|
-
"
|
|
476
|
-
"
|
|
477
|
-
"scope:
|
|
477
|
+
"playground-e2e": e2eProjectName,
|
|
478
|
+
"packages/playground/e2e": path.relative(cwd, targetE2eDir).replace(/\\/g, "/"),
|
|
479
|
+
"scope:playground": `scope:${featureName}`,
|
|
478
480
|
});
|
|
479
481
|
// Adjust project.json
|
|
480
482
|
const projJsonPath = path.join(targetE2eDir, "project.json");
|
|
@@ -704,12 +706,10 @@ pnpm type-check
|
|
|
704
706
|
const s = p.spinner();
|
|
705
707
|
s.start("Scaffolding standalone TestSpectra project...");
|
|
706
708
|
copyRecursive(templateDir, cwd);
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
fs.writeFileSync(pnpmWorkspacePath, pnpmWsContent, "utf-8");
|
|
712
|
-
}
|
|
709
|
+
const standaloneWsPath = path.join(cwd, "pnpm-workspace.yaml");
|
|
710
|
+
if (!fs.existsSync(standaloneWsPath) || options.force) {
|
|
711
|
+
const pnpmWsContent = `allowBuilds:\n chromedriver: true\n edgedriver: true\n geckodriver: true\n`;
|
|
712
|
+
fs.writeFileSync(standaloneWsPath, pnpmWsContent, "utf-8");
|
|
713
713
|
}
|
|
714
714
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
715
715
|
ensureVsCodeExtensions(cwd);
|
package/dist/commands/run.d.ts
CHANGED
package/dist/commands/run.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Reporter } from "../runner/reporter.js";
|
|
|
6
6
|
import { TypeGenerator } from "../types/generator.js";
|
|
7
7
|
export async function runCommand(specPath, options = {}) {
|
|
8
8
|
const cwd = process.cwd();
|
|
9
|
+
const effectiveSpecPath = specPath || options.spec;
|
|
9
10
|
// Ensure latest ambient types are up-to-date
|
|
10
11
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
11
12
|
const config = await ConfigLoader.loadConfig(cwd);
|
|
@@ -13,11 +14,11 @@ export async function runCommand(specPath, options = {}) {
|
|
|
13
14
|
config.webConfig.headlessMode = options.headless;
|
|
14
15
|
}
|
|
15
16
|
const platform = options.target || "web";
|
|
16
|
-
// Find workspace root for centralized .testspectra
|
|
17
|
+
// Find workspace root for centralized .testspectra (monorepo root or current test project)
|
|
17
18
|
let workspaceRoot = cwd;
|
|
18
19
|
let cur = cwd;
|
|
19
20
|
while (cur !== path.dirname(cur)) {
|
|
20
|
-
if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml")) || fs.existsSync(path.join(cur, "nx.json"))
|
|
21
|
+
if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml")) || fs.existsSync(path.join(cur, "nx.json"))) {
|
|
21
22
|
workspaceRoot = cur;
|
|
22
23
|
break;
|
|
23
24
|
}
|
|
@@ -30,18 +31,25 @@ export async function runCommand(specPath, options = {}) {
|
|
|
30
31
|
// Determine suite/test case name
|
|
31
32
|
let suiteName = "default";
|
|
32
33
|
let testCases = [];
|
|
33
|
-
if (
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
34
|
+
if (effectiveSpecPath) {
|
|
35
|
+
const relSpec = path.isAbsolute(effectiveSpecPath) ? path.relative(cwd, effectiveSpecPath) : effectiveSpecPath;
|
|
36
|
+
const normalized = relSpec.replace(/\\/g, "/");
|
|
37
|
+
const parts = normalized.split("/");
|
|
38
|
+
if (parts.length >= 2 && (parts[0] === "specs" || parts[0] === "suites")) {
|
|
39
|
+
suiteName = parts[1];
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const parsed = path.parse(normalized);
|
|
43
|
+
suiteName = parsed.name.replace(/\.(web|android|ios|common|test|spec)/g, "") || "default";
|
|
44
|
+
}
|
|
37
45
|
testCases.push({
|
|
38
|
-
id:
|
|
39
|
-
title:
|
|
46
|
+
id: normalized,
|
|
47
|
+
title: normalized,
|
|
40
48
|
executionOrder: 1,
|
|
41
49
|
});
|
|
42
50
|
}
|
|
43
51
|
else {
|
|
44
|
-
// 1. Scan specs/ directory (
|
|
52
|
+
// 1. Scan specs/ directory (specs/<Suite>/<CaseId>/web.test.ts)
|
|
45
53
|
const specsDir = path.join(cwd, "specs");
|
|
46
54
|
if (fs.existsSync(specsDir)) {
|
|
47
55
|
const suiteEntries = fs.readdirSync(specsDir, { withFileTypes: true });
|
|
@@ -55,11 +63,11 @@ export async function runCommand(specPath, options = {}) {
|
|
|
55
63
|
if (cEntry.name.startsWith(".") || cEntry.name === "hooks")
|
|
56
64
|
continue;
|
|
57
65
|
if (cEntry.isDirectory()) {
|
|
58
|
-
testCases.push({ id: cEntry.name, title: `${currentSuite} / ${cEntry.name}` });
|
|
66
|
+
testCases.push({ id: path.join("specs", currentSuite, cEntry.name).replace(/\\/g, "/"), title: `${currentSuite} / ${cEntry.name}` });
|
|
59
67
|
}
|
|
60
68
|
else if (cEntry.isFile() && (cEntry.name.endsWith(".test.ts") || cEntry.name.endsWith(".spec.ts"))) {
|
|
61
|
-
const id = cEntry.name.replace(
|
|
62
|
-
testCases.push({ id, title: `${currentSuite} / ${
|
|
69
|
+
const id = path.join("specs", currentSuite, cEntry.name).replace(/\\/g, "/");
|
|
70
|
+
testCases.push({ id, title: `${currentSuite} / ${cEntry.name}` });
|
|
63
71
|
}
|
|
64
72
|
}
|
|
65
73
|
}
|
|
@@ -77,7 +85,7 @@ export async function runCommand(specPath, options = {}) {
|
|
|
77
85
|
for (const cEntry of caseEntries) {
|
|
78
86
|
if (cEntry.name.startsWith(".") || cEntry.name === "hooks" || !cEntry.isDirectory())
|
|
79
87
|
continue;
|
|
80
|
-
testCases.push({ id: cEntry.name, title: `${currentSuite} / ${cEntry.name}` });
|
|
88
|
+
testCases.push({ id: path.join("suites", currentSuite, cEntry.name).replace(/\\/g, "/"), title: `${currentSuite} / ${cEntry.name}` });
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
}
|
|
@@ -93,6 +101,7 @@ export async function runCommand(specPath, options = {}) {
|
|
|
93
101
|
? path.resolve(options.output)
|
|
94
102
|
: path.join(appDataPath, "reports/result.json");
|
|
95
103
|
console.log(`\x1b[36m[TestSpectra]\x1b[0m Starting execution on target \x1b[1m${platform}\x1b[0m...`);
|
|
104
|
+
console.log(`\x1b[36m[TestSpectra]\x1b[0m Base URL: \x1b[33m${config.webConfig.baseUrl}\x1b[0m`);
|
|
96
105
|
console.log(`\x1b[36m[TestSpectra]\x1b[0m Suite: ${suiteName} (${testCases.length} case(s))\n`);
|
|
97
106
|
const reporter = new Reporter();
|
|
98
107
|
try {
|
package/dist/index.js
CHANGED
|
@@ -45,6 +45,7 @@ export function createCliProgram() {
|
|
|
45
45
|
program
|
|
46
46
|
.command("run [path]")
|
|
47
47
|
.description("Execute test suite or individual test case")
|
|
48
|
+
.option("-s, --spec <path>", "Spec file path to execute")
|
|
48
49
|
.option("-t, --target <target>", "Target platform: web, android, ios, common", "web")
|
|
49
50
|
.option("-d, --device <device>", "Target device name or UDID")
|
|
50
51
|
.option("--headless", "Run in headless mode")
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import http from "http";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
export class DemoAppServer {
|
|
10
|
+
server = null;
|
|
11
|
+
port = 0;
|
|
12
|
+
url = "";
|
|
13
|
+
static findDemoAppDir(cwd) {
|
|
14
|
+
// 1. Check upward from cwd
|
|
15
|
+
let cur = cwd;
|
|
16
|
+
while (cur !== path.dirname(cur)) {
|
|
17
|
+
const cand = path.join(cur, "apps", "demo-web-app");
|
|
18
|
+
if (fs.existsSync(cand) && fs.existsSync(path.join(cand, "package.json"))) {
|
|
19
|
+
return cand;
|
|
20
|
+
}
|
|
21
|
+
cur = path.dirname(cur);
|
|
22
|
+
}
|
|
23
|
+
// 2. Check relative to CLI package location
|
|
24
|
+
const relCand = path.resolve(__dirname, "../../../apps/demo-web-app");
|
|
25
|
+
if (fs.existsSync(relCand) && fs.existsSync(path.join(relCand, "package.json"))) {
|
|
26
|
+
return relCand;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
async start(cwd) {
|
|
31
|
+
const demoDir = DemoAppServer.findDemoAppDir(cwd);
|
|
32
|
+
if (!demoDir) {
|
|
33
|
+
throw new Error("Could not locate 'apps/demo-web-app'. Ensure you are running within the TestSpectra workspace.");
|
|
34
|
+
}
|
|
35
|
+
const distDir = path.join(demoDir, "dist");
|
|
36
|
+
if (!fs.existsSync(distDir) || !fs.existsSync(path.join(distDir, "index.html"))) {
|
|
37
|
+
console.log(chalk.cyan("[TestSpectra] ") +
|
|
38
|
+
chalk.dim("Building demo web app bundle (apps/demo-web-app)..."));
|
|
39
|
+
execSync("pnpm --filter @testspectra/demo-web-app build", {
|
|
40
|
+
cwd: path.resolve(demoDir, "../.."),
|
|
41
|
+
stdio: "inherit",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const mimeTypes = {
|
|
45
|
+
".html": "text/html; charset=utf-8",
|
|
46
|
+
".js": "text/javascript; charset=utf-8",
|
|
47
|
+
".css": "text/css; charset=utf-8",
|
|
48
|
+
".json": "application/json",
|
|
49
|
+
".svg": "image/svg+xml",
|
|
50
|
+
".png": "image/png",
|
|
51
|
+
".jpg": "image/jpeg",
|
|
52
|
+
".jpeg": "image/jpeg",
|
|
53
|
+
".ico": "image/x-icon",
|
|
54
|
+
".woff": "font/woff",
|
|
55
|
+
".woff2": "font/woff2",
|
|
56
|
+
".ttf": "font/ttf",
|
|
57
|
+
};
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
this.server = http.createServer((req, res) => {
|
|
60
|
+
const rawUrl = req.url || "/";
|
|
61
|
+
const cleanPath = rawUrl.split("?")[0].split("#")[0];
|
|
62
|
+
let filePath = path.join(distDir, cleanPath === "/" ? "index.html" : cleanPath);
|
|
63
|
+
// Fallback to index.html for SPA client-side routing
|
|
64
|
+
if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
|
|
65
|
+
filePath = path.join(distDir, "index.html");
|
|
66
|
+
}
|
|
67
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
68
|
+
const contentType = mimeTypes[ext] || "application/octet-stream";
|
|
69
|
+
fs.readFile(filePath, (err, content) => {
|
|
70
|
+
if (err) {
|
|
71
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
72
|
+
res.end("Internal Server Error");
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
res.writeHead(200, {
|
|
76
|
+
"Content-Type": contentType,
|
|
77
|
+
"Cache-Control": "no-cache",
|
|
78
|
+
"Access-Control-Allow-Origin": "*",
|
|
79
|
+
});
|
|
80
|
+
res.end(content);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
this.server.listen(0, "127.0.0.1", () => {
|
|
85
|
+
const addr = this.server?.address();
|
|
86
|
+
if (addr && typeof addr === "object") {
|
|
87
|
+
this.port = addr.port;
|
|
88
|
+
this.url = `http://127.0.0.1:${this.port}`;
|
|
89
|
+
resolve(this.url);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
reject(new Error("Failed to bind demo preview server address"));
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
this.server.on("error", reject);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
stop() {
|
|
99
|
+
if (this.server) {
|
|
100
|
+
this.server.close();
|
|
101
|
+
this.server = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -12,6 +12,6 @@ export declare class TypeGenerator {
|
|
|
12
12
|
static getPageObjectsDirsForScope(scopeDir: string, sharedDir?: string): string[];
|
|
13
13
|
static getEntityDirsForScope(entityType: "actions" | "steps" | "fixtures", scopeDir: string, sharedDir?: string): string[];
|
|
14
14
|
static generateDeclarationsForScope(cwd: string, outputTypesDir: string, scopeDir: string, sharedDir?: string): Record<string, string>;
|
|
15
|
-
static generateFixturesDeclarationForScope(scopeDir: string, sharedDir?: string): string;
|
|
15
|
+
static generateFixturesDeclarationForScope(cwd: string, outputTypesDir: string, scopeDir: string, sharedDir?: string): string;
|
|
16
16
|
static writeDeclarationFiles(cwd: string): void;
|
|
17
17
|
}
|
package/dist/types/generator.js
CHANGED
|
@@ -2,10 +2,10 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
const PLATFORM_HIERARCHY = {
|
|
4
4
|
web: ["web", "common"],
|
|
5
|
-
ios: ["ios", "mobile", "common"],
|
|
6
|
-
android: ["android", "mobile", "common"],
|
|
7
|
-
mobile: ["mobile", "common"],
|
|
8
|
-
common: ["common"],
|
|
5
|
+
ios: ["ios", "mobile", "common", "web"],
|
|
6
|
+
android: ["android", "mobile", "common", "web"],
|
|
7
|
+
mobile: ["mobile", "common", "android", "ios", "web"],
|
|
8
|
+
common: ["common", "web", "mobile", "android", "ios"],
|
|
9
9
|
};
|
|
10
10
|
export class TypeGenerator {
|
|
11
11
|
/**
|
|
@@ -21,7 +21,12 @@ export class TypeGenerator {
|
|
|
21
21
|
path.join(cwd, "testing"),
|
|
22
22
|
];
|
|
23
23
|
for (const cand of sharedCandidates) {
|
|
24
|
-
if (fs.existsSync(cand) &&
|
|
24
|
+
if (fs.existsSync(cand) &&
|
|
25
|
+
(fs.existsSync(path.join(cand, "page-objects")) ||
|
|
26
|
+
fs.existsSync(path.join(cand, "support")) ||
|
|
27
|
+
fs.existsSync(path.join(cand, "steps")) ||
|
|
28
|
+
fs.existsSync(path.join(cand, "actions")) ||
|
|
29
|
+
fs.existsSync(path.join(cand, "fixtures")))) {
|
|
25
30
|
if (!scopes.some((s) => s.dir === cand)) {
|
|
26
31
|
scopes.push({ name: "shared", dir: cand, isShared: true });
|
|
27
32
|
}
|
|
@@ -124,9 +129,15 @@ export class TypeGenerator {
|
|
|
124
129
|
scanDir(scopeDir, 6);
|
|
125
130
|
}
|
|
126
131
|
else {
|
|
127
|
-
const candidates = [
|
|
132
|
+
const candidates = [
|
|
133
|
+
path.join(scopeDir, entityType),
|
|
134
|
+
path.join(scopeDir, "support", entityType),
|
|
135
|
+
path.join(scopeDir, "support", "testing", entityType),
|
|
136
|
+
];
|
|
128
137
|
if (sharedDir) {
|
|
129
138
|
candidates.push(path.join(sharedDir, entityType));
|
|
139
|
+
candidates.push(path.join(sharedDir, "support", entityType));
|
|
140
|
+
candidates.push(path.join(sharedDir, "support", "testing", entityType));
|
|
130
141
|
}
|
|
131
142
|
for (const c of candidates) {
|
|
132
143
|
if (fs.existsSync(c) && !dirs.includes(c))
|
|
@@ -244,7 +255,11 @@ export class TypeGenerator {
|
|
|
244
255
|
if (matchedFile) {
|
|
245
256
|
if (!matchedFile.startsWith("."))
|
|
246
257
|
matchedFile = `./${matchedFile}`;
|
|
247
|
-
|
|
258
|
+
const camel = actionName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
259
|
+
content += ` "${actionName}": (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
260
|
+
if (camel !== actionName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
261
|
+
content += ` ${camel}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
262
|
+
}
|
|
248
263
|
declaredActions.add(actionName);
|
|
249
264
|
}
|
|
250
265
|
}
|
|
@@ -258,7 +273,11 @@ export class TypeGenerator {
|
|
|
258
273
|
let rel = path.relative(outputTypesDir, path.join(actionsDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
259
274
|
if (!rel.startsWith("."))
|
|
260
275
|
rel = `./${rel}`;
|
|
261
|
-
|
|
276
|
+
const camel = actionName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
277
|
+
content += ` "${actionName}": (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
278
|
+
if (camel !== actionName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
279
|
+
content += ` ${camel}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
280
|
+
}
|
|
262
281
|
declaredActions.add(actionName);
|
|
263
282
|
}
|
|
264
283
|
}
|
|
@@ -298,7 +317,11 @@ export class TypeGenerator {
|
|
|
298
317
|
if (matchedFile) {
|
|
299
318
|
if (!matchedFile.startsWith("."))
|
|
300
319
|
matchedFile = `./${matchedFile}`;
|
|
301
|
-
|
|
320
|
+
const camel = stepName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
321
|
+
content += ` "${stepName}": (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
322
|
+
if (camel !== stepName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
323
|
+
content += ` ${camel}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
324
|
+
}
|
|
302
325
|
declaredSteps.add(stepName);
|
|
303
326
|
}
|
|
304
327
|
}
|
|
@@ -312,7 +335,11 @@ export class TypeGenerator {
|
|
|
312
335
|
let rel = path.relative(outputTypesDir, path.join(stepsDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
313
336
|
if (!rel.startsWith("."))
|
|
314
337
|
rel = `./${rel}`;
|
|
315
|
-
|
|
338
|
+
const camel = stepName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
339
|
+
content += ` "${stepName}": (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
340
|
+
if (camel !== stepName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
341
|
+
content += ` ${camel}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
342
|
+
}
|
|
316
343
|
declaredSteps.add(stepName);
|
|
317
344
|
}
|
|
318
345
|
}
|
|
@@ -324,7 +351,7 @@ export class TypeGenerator {
|
|
|
324
351
|
}
|
|
325
352
|
return results;
|
|
326
353
|
}
|
|
327
|
-
static generateFixturesDeclarationForScope(scopeDir, sharedDir) {
|
|
354
|
+
static generateFixturesDeclarationForScope(cwd, outputTypesDir, scopeDir, sharedDir) {
|
|
328
355
|
const fixturesDirs = this.getEntityDirsForScope("fixtures", scopeDir, sharedDir);
|
|
329
356
|
let content = `// Auto-generated ambient fixture declarations for TestSpectra\n`;
|
|
330
357
|
content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
|
|
@@ -346,7 +373,15 @@ export class TypeGenerator {
|
|
|
346
373
|
const varName = parsed.name.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
347
374
|
if (declaredFixtures.has(varName))
|
|
348
375
|
continue;
|
|
349
|
-
|
|
376
|
+
if (entry.name.endsWith(".json")) {
|
|
377
|
+
let rel = path.relative(outputTypesDir, path.join(dir, entry.name)).replace(/\\/g, "/");
|
|
378
|
+
if (!rel.startsWith("."))
|
|
379
|
+
rel = `./${rel}`;
|
|
380
|
+
content += ` readonly ${varName}: typeof import('${rel}');\n`;
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
content += ` readonly ${varName}: string;\n`;
|
|
384
|
+
}
|
|
350
385
|
declaredFixtures.add(varName);
|
|
351
386
|
}
|
|
352
387
|
}
|
|
@@ -368,7 +403,7 @@ export class TypeGenerator {
|
|
|
368
403
|
const scopes = this.getEntityScopes(cwd);
|
|
369
404
|
const sharedScope = scopes.find((s) => s.isShared);
|
|
370
405
|
// 1. Generate Global / Standalone Root Types (or fallback)
|
|
371
|
-
const rootFixtures = this.generateFixturesDeclarationForScope(cwd);
|
|
406
|
+
const rootFixtures = this.generateFixturesDeclarationForScope(cwd, rootTypesDir, cwd);
|
|
372
407
|
fs.writeFileSync(path.join(rootTypesDir, "fixtures.d.ts"), rootFixtures, "utf-8");
|
|
373
408
|
const rootDeclarations = this.generateDeclarationsForScope(cwd, rootTypesDir, cwd);
|
|
374
409
|
for (const [platform, content] of Object.entries(rootDeclarations)) {
|
|
@@ -379,7 +414,7 @@ export class TypeGenerator {
|
|
|
379
414
|
const sharedTypesDir = path.join(rootTypesDir, "shared");
|
|
380
415
|
if (!fs.existsSync(sharedTypesDir))
|
|
381
416
|
fs.mkdirSync(sharedTypesDir, { recursive: true });
|
|
382
|
-
const sharedFixtures = this.generateFixturesDeclarationForScope(sharedScope.dir);
|
|
417
|
+
const sharedFixtures = this.generateFixturesDeclarationForScope(cwd, sharedTypesDir, sharedScope.dir);
|
|
383
418
|
fs.writeFileSync(path.join(sharedTypesDir, "fixtures.d.ts"), sharedFixtures, "utf-8");
|
|
384
419
|
// Shared scope has NO feature dependencies (only shared itself)
|
|
385
420
|
const sharedDeclarations = this.generateDeclarationsForScope(cwd, sharedTypesDir, sharedScope.dir);
|
|
@@ -393,7 +428,7 @@ export class TypeGenerator {
|
|
|
393
428
|
const featureTypesDir = path.join(rootTypesDir, "features", feature.name);
|
|
394
429
|
if (!fs.existsSync(featureTypesDir))
|
|
395
430
|
fs.mkdirSync(featureTypesDir, { recursive: true });
|
|
396
|
-
const featureFixtures = this.generateFixturesDeclarationForScope(feature.dir, sharedScope?.dir);
|
|
431
|
+
const featureFixtures = this.generateFixturesDeclarationForScope(cwd, featureTypesDir, feature.dir, sharedScope?.dir);
|
|
397
432
|
fs.writeFileSync(path.join(featureTypesDir, "fixtures.d.ts"), featureFixtures, "utf-8");
|
|
398
433
|
// Feature scope gets (feature + shared) ONLY
|
|
399
434
|
const featureDeclarations = this.generateDeclarationsForScope(cwd, featureTypesDir, feature.dir, sharedScope?.dir);
|