@testspectra/cli 1.0.4 → 1.0.6

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.
@@ -0,0 +1,138 @@
1
+ import { executeSingleMatcher, resolveElement } from "../matchers.js";
2
+ export class SingleElementRunner {
3
+ _target;
4
+ _action = null;
5
+ _assertions = [];
6
+ constructor(target, action) {
7
+ this._target = target;
8
+ this._action = action || null;
9
+ }
10
+ // --- Chained Actions on current target ---
11
+ click() {
12
+ const target = this.requireTarget("click");
13
+ this._action = async () => {
14
+ const el = await resolveElement(target);
15
+ await el.click();
16
+ };
17
+ return this;
18
+ }
19
+ doubleClick() {
20
+ const target = this.requireTarget("doubleClick");
21
+ this._action = async () => {
22
+ const el = await resolveElement(target);
23
+ await el.doubleClick();
24
+ };
25
+ return this;
26
+ }
27
+ longPress(durationMs = 1000) {
28
+ const target = this.requireTarget("longPress");
29
+ this._action = async () => {
30
+ const el = await resolveElement(target);
31
+ if (typeof el.touchAction === "function") {
32
+ await el.touchAction([
33
+ { action: "longPress", ms: durationMs },
34
+ { action: "release" },
35
+ ]);
36
+ }
37
+ else {
38
+ await el.click();
39
+ }
40
+ };
41
+ return this;
42
+ }
43
+ type(value, options) {
44
+ const target = this.requireTarget("type");
45
+ this._action = async () => {
46
+ const el = await resolveElement(target);
47
+ if (options?.clearFirst) {
48
+ await el.clearValue();
49
+ }
50
+ await el.setValue(value);
51
+ };
52
+ return this;
53
+ }
54
+ clear() {
55
+ const target = this.requireTarget("clear");
56
+ this._action = async () => {
57
+ const el = await resolveElement(target);
58
+ await el.clearValue();
59
+ };
60
+ return this;
61
+ }
62
+ select(value) {
63
+ const target = this.requireTarget("select");
64
+ this._action = async () => {
65
+ const el = await resolveElement(target);
66
+ await el.selectByVisibleText(value);
67
+ };
68
+ return this;
69
+ }
70
+ hover() {
71
+ const target = this.requireTarget("hover");
72
+ this._action = async () => {
73
+ const el = await resolveElement(target);
74
+ await el.moveTo();
75
+ };
76
+ return this;
77
+ }
78
+ waitForElement(timeoutMs = 10000) {
79
+ const target = this.requireTarget("waitForElement");
80
+ this._action = async () => {
81
+ const el = await resolveElement(target);
82
+ await el.waitForDisplayed({ timeout: timeoutMs });
83
+ };
84
+ return this;
85
+ }
86
+ should(a, b, ...rest) {
87
+ const isFirstArgMatcher = typeof a === "string" &&
88
+ (a.includes(".") ||
89
+ a.includes("exist") ||
90
+ a.includes("visible") ||
91
+ a.includes("enabled") ||
92
+ a.includes("disabled") ||
93
+ a.includes("selected") ||
94
+ a.includes("checked"));
95
+ if (b !== undefined && !isFirstArgMatcher) {
96
+ // Form: should(target, matcher, ...args)
97
+ this._assertions.push({
98
+ target: a,
99
+ matcher: b,
100
+ args: rest,
101
+ });
102
+ }
103
+ else {
104
+ // Form: should(matcher, ...args) -> on current target
105
+ this._assertions.push({
106
+ target: this._target,
107
+ matcher: a,
108
+ args: b !== undefined ? [b, ...rest] : rest,
109
+ });
110
+ }
111
+ return this;
112
+ }
113
+ // --- Execution when awaited ---
114
+ async then(onfulfilled, onrejected) {
115
+ try {
116
+ if (this._action) {
117
+ await this._action();
118
+ }
119
+ for (const assert of this._assertions) {
120
+ await executeSingleMatcher(assert.target, assert.matcher, assert.args);
121
+ }
122
+ const res = (onfulfilled ? await onfulfilled() : undefined);
123
+ return res;
124
+ }
125
+ catch (err) {
126
+ if (onrejected) {
127
+ return onrejected(err);
128
+ }
129
+ throw err;
130
+ }
131
+ }
132
+ requireTarget(actionName) {
133
+ if (!this._target) {
134
+ throw new Error(`Cannot execute '${actionName}' without specifying an element target.`);
135
+ }
136
+ return this._target;
137
+ }
138
+ }
@@ -0,0 +1,34 @@
1
+ import { ClickOptions, ElementTarget, KeyOption, LongPressOptions, ScrollOptions, SwipeOptions, TypeOptions } from "./types.js";
2
+ import { SingleElementRunner } from "./runner/single.js";
3
+ import { MultiElementRunner } from "./runner/collection.js";
4
+ /**
5
+ * Main Spectra testing API orchestrator.
6
+ * Combines direct actions, single element queries (get), and collections (getAll).
7
+ */
8
+ export declare class SpectraStatic {
9
+ /**
10
+ * Targets a single element by selector or Page Object element.
11
+ */
12
+ get(target: ElementTarget): SingleElementRunner;
13
+ /**
14
+ * Targets multiple elements / collections by selector.
15
+ */
16
+ getAll(selector: string): MultiElementRunner;
17
+ navigate(url: string): SingleElementRunner;
18
+ back(): SingleElementRunner;
19
+ refresh(): SingleElementRunner;
20
+ click(target: ElementTarget, textOrOptions?: string | ClickOptions): SingleElementRunner;
21
+ doubleClick(target: ElementTarget, textOrOptions?: string | ClickOptions): SingleElementRunner;
22
+ longPress(target: ElementTarget, options?: LongPressOptions | number): SingleElementRunner;
23
+ type(target: ElementTarget, value: string, options?: TypeOptions): SingleElementRunner;
24
+ clear(target: ElementTarget): SingleElementRunner;
25
+ select(target: ElementTarget, value: string): SingleElementRunner;
26
+ hover(target: ElementTarget): SingleElementRunner;
27
+ pressKey(key: KeyOption | string): SingleElementRunner;
28
+ dragDrop(sourceTarget: ElementTarget, destTarget: ElementTarget): SingleElementRunner;
29
+ scroll(options?: ScrollOptions): SingleElementRunner;
30
+ swipe(options: SwipeOptions): SingleElementRunner;
31
+ wait(durationMs: number): SingleElementRunner;
32
+ waitForElement(target: ElementTarget, timeoutMs?: number): SingleElementRunner;
33
+ }
34
+ export declare const Spectra: SpectraStatic;
@@ -0,0 +1,156 @@
1
+ import { SingleElementRunner } from "./runner/single.js";
2
+ import { MultiElementRunner } from "./runner/collection.js";
3
+ import { resolveElement } from "./matchers.js";
4
+ /**
5
+ * Main Spectra testing API orchestrator.
6
+ * Combines direct actions, single element queries (get), and collections (getAll).
7
+ */
8
+ export class SpectraStatic {
9
+ /**
10
+ * Targets a single element by selector or Page Object element.
11
+ */
12
+ get(target) {
13
+ return new SingleElementRunner(target);
14
+ }
15
+ /**
16
+ * Targets multiple elements / collections by selector.
17
+ */
18
+ getAll(selector) {
19
+ return new MultiElementRunner(selector);
20
+ }
21
+ // --- Browser & Navigation Actions ---
22
+ navigate(url) {
23
+ return new SingleElementRunner(undefined, async () => {
24
+ await browser.url(url);
25
+ });
26
+ }
27
+ back() {
28
+ return new SingleElementRunner(undefined, async () => {
29
+ await browser.back();
30
+ });
31
+ }
32
+ refresh() {
33
+ return new SingleElementRunner(undefined, async () => {
34
+ await browser.refresh();
35
+ });
36
+ }
37
+ // --- Direct Interaction Actions ---
38
+ click(target, textOrOptions) {
39
+ return new SingleElementRunner(target, async () => {
40
+ const el = await resolveElement(target);
41
+ await el.click();
42
+ });
43
+ }
44
+ doubleClick(target, textOrOptions) {
45
+ return new SingleElementRunner(target, async () => {
46
+ const el = await resolveElement(target);
47
+ await el.doubleClick();
48
+ });
49
+ }
50
+ longPress(target, options) {
51
+ const duration = typeof options === "number" ? options : options?.duration || 1000;
52
+ return new SingleElementRunner(target, async () => {
53
+ const el = await resolveElement(target);
54
+ if (typeof el.touchAction === "function") {
55
+ await el.touchAction([
56
+ { action: "longPress", ms: duration },
57
+ { action: "release" },
58
+ ]);
59
+ }
60
+ else {
61
+ await el.click();
62
+ }
63
+ });
64
+ }
65
+ type(target, value, options) {
66
+ return new SingleElementRunner(target, async () => {
67
+ const el = await resolveElement(target);
68
+ if (options?.clearFirst) {
69
+ await el.clearValue();
70
+ }
71
+ await el.setValue(value);
72
+ });
73
+ }
74
+ clear(target) {
75
+ return new SingleElementRunner(target, async () => {
76
+ const el = await resolveElement(target);
77
+ await el.clearValue();
78
+ });
79
+ }
80
+ select(target, value) {
81
+ return new SingleElementRunner(target, async () => {
82
+ const el = await resolveElement(target);
83
+ await el.selectByVisibleText(value);
84
+ });
85
+ }
86
+ hover(target) {
87
+ return new SingleElementRunner(target, async () => {
88
+ const el = await resolveElement(target);
89
+ await el.moveTo();
90
+ });
91
+ }
92
+ pressKey(key) {
93
+ return new SingleElementRunner(undefined, async () => {
94
+ await browser.keys(key);
95
+ });
96
+ }
97
+ dragDrop(sourceTarget, destTarget) {
98
+ return new SingleElementRunner(sourceTarget, async () => {
99
+ const source = await resolveElement(sourceTarget);
100
+ const target = await resolveElement(destTarget);
101
+ await source.dragAndDrop(target);
102
+ });
103
+ }
104
+ // --- Gesture Actions ---
105
+ scroll(options) {
106
+ return new SingleElementRunner(options?.selector, async () => {
107
+ if (options?.selector) {
108
+ const el = await resolveElement(options.selector);
109
+ await el.scrollIntoView();
110
+ }
111
+ else {
112
+ await browser.execute((direction, pixels) => {
113
+ const delta = pixels || 500;
114
+ window.scrollBy({
115
+ top: direction === "up" ? -delta : direction === "down" ? delta : 0,
116
+ left: direction === "left" ? -delta : direction === "right" ? delta : 0,
117
+ behavior: "smooth",
118
+ });
119
+ }, options?.direction || "down", options?.pixels || 500);
120
+ }
121
+ });
122
+ }
123
+ swipe(options) {
124
+ return new SingleElementRunner(options.selector, async () => {
125
+ if (typeof browser.touchAction === "function") {
126
+ const distance = options.distance || 300;
127
+ await browser.touchAction([
128
+ { action: "press", x: 200, y: 500 },
129
+ {
130
+ action: "moveTo",
131
+ x: options.direction === "right" ? 200 + distance : options.direction === "left" ? 200 - distance : 200,
132
+ y: options.direction === "down" ? 500 + distance : options.direction === "up" ? 500 - distance : 500,
133
+ },
134
+ { action: "release" },
135
+ ]);
136
+ }
137
+ else {
138
+ // Fallback to web scroll
139
+ await this.scroll({ direction: options.direction, pixels: options.distance });
140
+ }
141
+ });
142
+ }
143
+ // --- Timing Actions ---
144
+ wait(durationMs) {
145
+ return new SingleElementRunner(undefined, async () => {
146
+ await browser.pause(durationMs);
147
+ });
148
+ }
149
+ waitForElement(target, timeoutMs = 10000) {
150
+ return new SingleElementRunner(target, async () => {
151
+ const el = await resolveElement(target);
152
+ await el.waitForDisplayed({ timeout: timeoutMs });
153
+ });
154
+ }
155
+ }
156
+ export const Spectra = new SpectraStatic();
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Database Canonical Action and Assertion Types
3
+ * Matches backend/src/models/test_step.rs
4
+ */
5
+ export type ActionKey = "navigate" | "click" | "type" | "clear" | "select" | "scroll" | "swipe" | "wait" | "waitForElement" | "pressKey" | "longPress" | "doubleClick" | "hover" | "dragDrop" | "back" | "refresh";
6
+ export type AssertionKey = "elementDisplayed" | "elementNotDisplayed" | "elementExists" | "elementEnabled" | "elementDisabled" | "textContains" | "textEquals" | "textNotContains" | "urlContains" | "urlEquals" | "valueEquals" | "valueContains" | "attributeEquals" | "attributeContains" | "hasClass" | "hasAttribute" | "isSelected" | "elementCount" | "elementCountGreaterThan" | "pageLoaded" | "noErrors";
7
+ export type KeyOption = "Enter" | "Tab" | "Escape" | "Backspace" | "Delete" | "ArrowUp" | "ArrowDown" | "ArrowLeft" | "ArrowRight" | "Space";
8
+ export type Direction = "up" | "down" | "left" | "right";
9
+ export type ElementTarget = string | WebdriverIO.Element | ChainablePromiseElement;
10
+ export interface ScrollOptions {
11
+ direction?: Direction;
12
+ pixels?: number;
13
+ selector?: ElementTarget;
14
+ }
15
+ export interface SwipeOptions {
16
+ direction: Direction;
17
+ distance?: number;
18
+ selector?: ElementTarget;
19
+ }
20
+ export interface LongPressOptions {
21
+ text?: string;
22
+ duration?: number;
23
+ }
24
+ export interface ClickOptions {
25
+ text?: string;
26
+ clickType?: "single" | "double";
27
+ }
28
+ export interface TypeOptions {
29
+ clearFirst?: boolean;
30
+ }
31
+ export type SingleElementMatcher = "be.visible" | "not.be.visible" | "exist" | "not.exist" | "be.enabled" | "be.disabled" | "be.checked" | "be.selected" | "have.value" | "contain.value" | "have.text" | "contain.text" | "have.class" | "have.attr" | "have.url" | "contain.url" | "have.title" | "contain.title";
32
+ export type MultiElementMatcher = "have.length" | "have.length.greaterThan" | "be.empty" | "exist";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Database Canonical Action and Assertion Types
3
+ * Matches backend/src/models/test_step.rs
4
+ */
5
+ export {};
@@ -44,7 +44,43 @@ export class TypeGenerator {
44
44
  content += ` }\n`;
45
45
  content += ` interface Browser {\n`;
46
46
  content += ` intercept<TData = unknown>(path: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH', fixture?: InterceptInput<TData>, options?: InterceptFixtureOptions): Promise<Mock>;\n`;
47
+ content += ` shouldHaveUrl(expectedUrl: string): Promise<void>;\n`;
48
+ content += ` shouldContainUrl(expectedUrl: string): Promise<void>;\n`;
49
+ content += ` shouldHaveTitle(expectedTitle: string): Promise<void>;\n`;
50
+ content += ` shouldContainTitle(expectedTitle: string): Promise<void>;\n`;
47
51
  content += ` }\n`;
52
+ content += ` interface Element {\n`;
53
+ content += ` shouldBeVisible(): Promise<Element>;\n`;
54
+ content += ` shouldNotBeVisible(): Promise<Element>;\n`;
55
+ content += ` shouldExist(): Promise<Element>;\n`;
56
+ content += ` shouldNotExist(): Promise<Element>;\n`;
57
+ content += ` shouldBeEnabled(): Promise<Element>;\n`;
58
+ content += ` shouldBeDisabled(): Promise<Element>;\n`;
59
+ content += ` shouldBeSelected(): Promise<Element>;\n`;
60
+ content += ` shouldBeChecked(): Promise<Element>;\n`;
61
+ content += ` shouldHaveValue(expectedValue: string): Promise<Element>;\n`;
62
+ content += ` shouldContainValue(expectedValue: string): Promise<Element>;\n`;
63
+ content += ` shouldHaveText(expectedText: string): Promise<Element>;\n`;
64
+ content += ` shouldContainText(expectedText: string): Promise<Element>;\n`;
65
+ content += ` shouldHaveClass(className: string): Promise<Element>;\n`;
66
+ content += ` shouldHaveAttribute(attributeName: string, expectedValue?: string): Promise<Element>;\n`;
67
+ content += ` }\n`;
68
+ content += `}\n\n`;
69
+ content += `interface Promise<T> {\n`;
70
+ content += ` shouldBeVisible(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;\n`;
71
+ content += ` shouldNotBeVisible(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;\n`;
72
+ content += ` shouldExist(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;\n`;
73
+ content += ` shouldNotExist(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;\n`;
74
+ content += ` shouldBeEnabled(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;\n`;
75
+ content += ` shouldBeDisabled(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;\n`;
76
+ content += ` shouldBeSelected(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;\n`;
77
+ content += ` shouldBeChecked(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;\n`;
78
+ content += ` shouldHaveValue(this: Promise<WebdriverIO.Element>, expectedValue: string): Promise<WebdriverIO.Element>;\n`;
79
+ content += ` shouldContainValue(this: Promise<WebdriverIO.Element>, expectedValue: string): Promise<WebdriverIO.Element>;\n`;
80
+ content += ` shouldHaveText(this: Promise<WebdriverIO.Element>, expectedText: string): Promise<WebdriverIO.Element>;\n`;
81
+ content += ` shouldContainText(this: Promise<WebdriverIO.Element>, expectedText: string): Promise<WebdriverIO.Element>;\n`;
82
+ content += ` shouldHaveClass(this: Promise<WebdriverIO.Element>, className: string): Promise<WebdriverIO.Element>;\n`;
83
+ content += ` shouldHaveAttribute(this: Promise<WebdriverIO.Element>, attributeName: string, expectedValue?: string): Promise<WebdriverIO.Element>;\n`;
48
84
  content += `}\n\n`;
49
85
  // 1. Page Objects Global Declarations (Hierarchical)
50
86
  // 2. Page Objects Global Declarations (Hierarchical)
@@ -75,8 +111,8 @@ export class TypeGenerator {
75
111
  }
76
112
  }
77
113
  }
78
- // 3. Actions Global Interface
79
- content += `\ninterface TestSpectraActions {\n`;
114
+ // 3. Actions Global Interface (Extends built-in @testspectra/cli Spectra)
115
+ content += `\ninterface TestSpectraActions extends Omit<typeof import('@testspectra/cli').Spectra, ''> {\n`;
80
116
  if (fs.existsSync(actionsDir)) {
81
117
  const actionEntries = fs.readdirSync(actionsDir, { withFileTypes: true });
82
118
  for (const entry of actionEntries) {
@@ -99,7 +135,7 @@ export class TypeGenerator {
99
135
  }
100
136
  }
101
137
  if (matchedFile) {
102
- content += ` ${actionName}: typeof import('${matchedFile}');\n`;
138
+ content += ` ${actionName}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
103
139
  }
104
140
  else {
105
141
  content += ` ${actionName}: (...args: any[]) => Promise<any>;\n`;
@@ -107,7 +143,8 @@ export class TypeGenerator {
107
143
  }
108
144
  else if (entry.isFile() && entry.name.endsWith(".ts")) {
109
145
  const actionName = entry.name.split(".")[0];
110
- content += ` ${actionName}: typeof import('../../actions/${entry.name.replace(".ts", ".js")}');\n`;
146
+ const modPath = `../../actions/${entry.name.replace(".ts", ".js")}`;
147
+ content += ` ${actionName}: (typeof import('${modPath}') extends { default: infer T } ? T : typeof import('${modPath}'));\n`;
111
148
  }
112
149
  }
113
150
  }
@@ -137,7 +174,7 @@ export class TypeGenerator {
137
174
  }
138
175
  }
139
176
  if (matchedFile) {
140
- content += ` ${stepName}: typeof import('${matchedFile}');\n`;
177
+ content += ` ${stepName}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
141
178
  }
142
179
  else {
143
180
  content += ` ${stepName}: (...args: any[]) => Promise<any>;\n`;
@@ -145,7 +182,8 @@ export class TypeGenerator {
145
182
  }
146
183
  else if (entry.isFile() && entry.name.endsWith(".ts")) {
147
184
  const stepName = entry.name.split(".")[0];
148
- content += ` ${stepName}: typeof import('../../steps/${entry.name.replace(".ts", ".js")}');\n`;
185
+ const modPath = `../../steps/${entry.name.replace(".ts", ".js")}`;
186
+ content += ` ${stepName}: (typeof import('${modPath}') extends { default: infer T } ? T : typeof import('${modPath}'));\n`;
149
187
  }
150
188
  }
151
189
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testspectra/cli",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "TestSpectra Zero-Config Cross-Platform Test Runner CLI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -294,14 +294,18 @@ export default defineConfig({
294
294
  return $('button[type="submit"]');
295
295
  }
296
296
 
297
+ static get flashAlert(): ChainablePromiseElement {
298
+ return $('#flash');
299
+ }
300
+
297
301
  static async open() {
298
- await browser.url('/login');
302
+ await Spectra.navigate('/login');
299
303
  }
300
304
 
301
305
  static async login(username: string, pass: string) {
302
- await this.usernameInput.setValue(username);
303
- await this.passwordInput.setValue(pass);
304
- await this.submitButton.click();
306
+ await Spectra.type(this.usernameInput, username);
307
+ await Spectra.type(this.passwordInput, pass);
308
+ await Spectra.click(this.submitButton);
305
309
  }
306
310
  }
307
311
  `;
@@ -320,14 +324,18 @@ export default defineConfig({
320
324
  return $('~login_button');
321
325
  }
322
326
 
327
+ static get welcomeText(): ChainablePromiseElement {
328
+ return $('~welcome_text');
329
+ }
330
+
323
331
  static async open() {
324
332
  // Mobile app startup
325
333
  }
326
334
 
327
335
  static async login(username: string, pass: string) {
328
- await this.usernameInput.setValue(username);
329
- await this.passwordInput.setValue(pass);
330
- await this.submitButton.click();
336
+ await Spectra.type(this.usernameInput, username);
337
+ await Spectra.type(this.passwordInput, pass);
338
+ await Spectra.click(this.submitButton);
331
339
  }
332
340
  }
333
341
  `;
@@ -386,34 +394,35 @@ export default defineConfig({
386
394
  `;
387
395
  fs.writeFileSync(path.join(cwd, "hooks/default/before.ios.hook.ts"), hookIos, "utf-8");
388
396
 
389
- // 10. Scaffold Specs (Pure test scripts, ZERO triple-slash lines)
390
- const specWeb = `it("should authenticate user using web locators and fixtures", async () => {
397
+ // 10. Scaffold Specs (Pure test scripts, ZERO triple-slash lines, ZERO raw selectors)
398
+ const specWeb = `it("should authenticate user using Page Objects and Spectra assertions", async () => {
391
399
  await browser.intercept("/api/status", "GET", Fixture.userData);
400
+
401
+ // 1. Navigation via Page Object
392
402
  await LoginPage.open();
393
- await LoginPage.login("tomsmith", "SuperSecretPassword!");
394
403
 
395
- const flash = await $("#flash");
396
- await expect(flash).toBeDisplayed();
404
+ // 2. High-level business flow via Shared Step & Page Object
405
+ await Step.loginUser("tomsmith", "SuperSecretPassword!");
406
+
407
+ // 3. Direct callable assertion on Page Object element
408
+ await LoginPage.flashAlert.shouldBeVisible();
409
+ await LoginPage.flashAlert.shouldContainText("You logged into a secure area!");
397
410
  });
398
411
  `;
399
412
  fs.writeFileSync(path.join(cwd, "specs/TC-LOGIN-01/web.test.ts"), specWeb, "utf-8");
400
413
 
401
- const specAndroid = `it("should authenticate user on Android Appium device", async () => {
402
- await LoginPage.open();
403
- await LoginPage.login("tomsmith", "SuperSecretPassword!");
414
+ const specAndroid = `it("should authenticate user on Android Appium device using Page Objects", async () => {
415
+ await Step.loginUser("tomsmith", "SuperSecretPassword!");
404
416
 
405
- const welcome = await $("~welcome_text");
406
- await expect(welcome).toBeDisplayed();
417
+ await LoginPage.welcomeText.shouldBeVisible();
407
418
  });
408
419
  `;
409
420
  fs.writeFileSync(path.join(cwd, "specs/TC-LOGIN-01/android.test.ts"), specAndroid, "utf-8");
410
421
 
411
- const specIos = `it("should authenticate user on iOS Appium device", async () => {
412
- await LoginPage.open();
413
- await LoginPage.login("tomsmith", "SuperSecretPassword!");
422
+ const specIos = `it("should authenticate user on iOS Appium device using Page Objects", async () => {
423
+ await Step.loginUser("tomsmith", "SuperSecretPassword!");
414
424
 
415
- const welcome = await $("~welcome_text");
416
- await expect(welcome).toBeDisplayed();
425
+ await LoginPage.welcomeText.shouldBeVisible();
417
426
  });
418
427
  `;
419
428
  fs.writeFileSync(path.join(cwd, "specs/TC-LOGIN-01/ios.test.ts"), specIos, "utf-8");
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import { runCommand } from "./commands/run.js";
7
7
  export * from "./config/schema.js";
8
8
  export * from "./config/loader.js";
9
9
  export * from "./types/generator.js";
10
+ export * from "./step/index.js";
10
11
  export { init as initTsPlugin, default as tsPlugin } from "./plugin.js";
11
12
 
12
13
  export function createCliProgram() {
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export * from "./matchers.js";
3
+ export * from "./runner/single.js";
4
+ export * from "./runner/collection.js";
5
+ export * from "./spectra.js";
6
+ export * from "./proto.js";