@testspectra/cli 1.0.7 → 1.0.10
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/dist/commands/init.js +58 -398
- package/dist/commands/run.js +18 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +8 -1
- package/templates/default/actions/applyDiscount/mobile.action.ts +6 -0
- package/templates/default/actions/applyDiscount/web.action.ts +6 -0
- package/templates/default/actions/verifyOtp/mobile.action.ts +6 -0
- package/templates/default/actions/verifyOtp/web.action.ts +6 -0
- package/templates/default/fixtures/couponCode.json +1 -0
- package/templates/default/fixtures/searchQuery.json +1 -0
- package/templates/default/fixtures/userData.json +1 -0
- package/templates/default/global-hooks/before.web.hook.ts +4 -0
- package/templates/default/package.json +18 -0
- package/templates/default/page-objects/LoginPage/mobile.ts +32 -0
- package/templates/default/page-objects/LoginPage/web.ts +32 -0
- package/templates/default/page-objects/ProductPage/mobile.ts +39 -0
- package/templates/default/page-objects/ProductPage/web.ts +38 -0
- package/templates/default/page-objects/SettingsPage/common.ts +26 -0
- package/templates/default/spectra.config.ts +61 -0
- package/templates/default/steps/loginUser/mobile.step.ts +4 -0
- package/templates/default/steps/loginUser/web.step.ts +4 -0
- package/templates/default/steps/searchProduct/mobile.step.ts +5 -0
- package/templates/default/steps/searchProduct/web.step.ts +5 -0
- package/templates/default/suites/Auth/TC-AUTH-01/android.test.ts +6 -0
- package/templates/default/suites/Auth/TC-AUTH-01/ios.test.ts +6 -0
- package/templates/default/suites/Auth/TC-AUTH-01/web.test.ts +13 -0
- package/templates/default/suites/Auth/TC-AUTH-02/android.test.ts +5 -0
- package/templates/default/suites/Auth/TC-AUTH-02/web.test.ts +5 -0
- package/templates/default/suites/Auth/hooks/before.android.hook.ts +3 -0
- package/templates/default/suites/Auth/hooks/before.ios.hook.ts +3 -0
- package/templates/default/suites/Auth/hooks/before.web.hook.ts +3 -0
- package/templates/default/suites/Product/TC-PROD-01/mobile.test.ts +8 -0
- package/templates/default/suites/Product/TC-PROD-01/web.test.ts +8 -0
- package/templates/default/suites/Product/TC-PROD-02/mobile.test.ts +6 -0
- package/templates/default/suites/Product/TC-PROD-02/web.test.ts +6 -0
- package/templates/default/suites/Product/hooks/before.web.hook.ts +3 -0
- package/templates/default/suites/Settings/TC-SET-01/common.test.ts +8 -0
- package/templates/default/tsconfig.android.json +39 -0
- package/templates/default/tsconfig.ios.json +39 -0
- package/templates/default/tsconfig.json +20 -0
- package/templates/default/tsconfig.web.json +45 -0
- package/CLI_IMPLEMENTATION_PLAN.md +0 -369
- package/src/commands/devices.ts +0 -41
- package/src/commands/doctor.ts +0 -57
- package/src/commands/init.ts +0 -470
- package/src/commands/run.ts +0 -102
- package/src/config/loader.ts +0 -82
- package/src/config/schema.ts +0 -489
- package/src/index.ts +0 -56
- package/src/plugin.ts +0 -81
- package/src/runner/bridge.ts +0 -146
- package/src/runner/reporter.ts +0 -64
- package/src/step/index.ts +0 -6
- package/src/step/matchers.ts +0 -131
- package/src/step/proto.ts +0 -163
- package/src/step/runner/collection.ts +0 -73
- package/src/step/runner/single.ts +0 -166
- package/src/step/spectra.ts +0 -185
- package/src/step/types.ts +0 -113
- package/src/types/generator.ts +0 -240
- package/src/types/webdriverio.d.ts +0 -46
- package/testspectra-cli-1.0.5.tgz +0 -0
- package/testspectra-cli-1.0.6.tgz +0 -0
- package/testspectra-cli-1.0.7.tgz +0 -0
- package/tsconfig.json +0 -17
package/src/runner/bridge.ts
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { spawn } from "child_process";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
import { ConfigData } from "../config/schema.js";
|
|
6
|
-
import { Reporter, TestRunResult } from "./reporter.js";
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
-
|
|
11
|
-
export interface RunOptions {
|
|
12
|
-
baseDir: string;
|
|
13
|
-
appDataPath: string;
|
|
14
|
-
platform: "web" | "android" | "ios" | "common";
|
|
15
|
-
suite: string;
|
|
16
|
-
testCases: Array<{ id: string; title: string; executionOrder?: number }>;
|
|
17
|
-
config: ConfigData;
|
|
18
|
-
targetDevice?: string;
|
|
19
|
-
outputJsonPath?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class RustCoreBridge {
|
|
23
|
-
static resolveBinaryPath(): string {
|
|
24
|
-
// 1. Look for precompiled release/debug binary in workspace target
|
|
25
|
-
const workspaceRoot = path.resolve(__dirname, "../../..");
|
|
26
|
-
const candidatePaths = [
|
|
27
|
-
path.join(workspaceRoot, "target/debug/testspectra-runner"),
|
|
28
|
-
path.join(workspaceRoot, "target/release/testspectra-runner"),
|
|
29
|
-
path.join(workspaceRoot, "core/test-runner/target/debug/testspectra-runner"),
|
|
30
|
-
path.join(workspaceRoot, "core/test-runner/target/release/testspectra-runner"),
|
|
31
|
-
path.join(__dirname, "../../bin/testspectra-runner"),
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
for (const p of candidatePaths) {
|
|
35
|
-
if (fs.existsSync(p)) {
|
|
36
|
-
return p;
|
|
37
|
-
}
|
|
38
|
-
if (fs.existsSync(`${p}.exe`)) {
|
|
39
|
-
return `${p}.exe`;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return "cargo"; // Fallback to cargo run
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
static async run(options: RunOptions, reporter: Reporter): Promise<TestRunResult> {
|
|
47
|
-
const binPath = this.resolveBinaryPath();
|
|
48
|
-
const payload = JSON.stringify({
|
|
49
|
-
baseDir: options.baseDir,
|
|
50
|
-
appDataPath: options.appDataPath,
|
|
51
|
-
platform: options.platform,
|
|
52
|
-
suite: options.suite,
|
|
53
|
-
testCases: options.testCases,
|
|
54
|
-
config: options.config,
|
|
55
|
-
targetDevice: options.targetDevice,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
return new Promise<TestRunResult>((resolve, reject) => {
|
|
59
|
-
let child;
|
|
60
|
-
if (binPath === "cargo") {
|
|
61
|
-
const workspaceRoot = path.resolve(__dirname, "../../..");
|
|
62
|
-
child = spawn(
|
|
63
|
-
"cargo",
|
|
64
|
-
["run", "--manifest-path", "core/test-runner/Cargo.toml", "--bin", "testspectra-runner", "--", payload],
|
|
65
|
-
{
|
|
66
|
-
cwd: workspaceRoot,
|
|
67
|
-
env: { ...process.env, RUST_LOG: "info" },
|
|
68
|
-
}
|
|
69
|
-
);
|
|
70
|
-
} else {
|
|
71
|
-
child = spawn(binPath, [payload], {
|
|
72
|
-
env: { ...process.env, RUST_LOG: "info" },
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let runStatus = "failed";
|
|
77
|
-
let runDuration = "0s";
|
|
78
|
-
|
|
79
|
-
child.stdout.on("data", (data) => {
|
|
80
|
-
const lines = data.toString().split("\n");
|
|
81
|
-
for (const line of lines) {
|
|
82
|
-
if (!line.trim()) continue;
|
|
83
|
-
try {
|
|
84
|
-
const parsed = JSON.parse(line);
|
|
85
|
-
if (parsed.type === "log") {
|
|
86
|
-
reporter.addLog({
|
|
87
|
-
timestamp: parsed.timestamp,
|
|
88
|
-
level: parsed.level,
|
|
89
|
-
message: parsed.message,
|
|
90
|
-
});
|
|
91
|
-
} else if (parsed.type === "network") {
|
|
92
|
-
reporter.addNetwork({
|
|
93
|
-
requestId: parsed.requestId,
|
|
94
|
-
url: parsed.url,
|
|
95
|
-
method: parsed.method,
|
|
96
|
-
status: parsed.status,
|
|
97
|
-
});
|
|
98
|
-
} else if (parsed.type === "complete") {
|
|
99
|
-
runStatus = parsed.status;
|
|
100
|
-
runDuration = parsed.duration;
|
|
101
|
-
} else if (parsed.type === "error") {
|
|
102
|
-
reporter.addLog({
|
|
103
|
-
timestamp: new Date().toLocaleTimeString(),
|
|
104
|
-
level: "ERROR",
|
|
105
|
-
message: parsed.message,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
} catch {
|
|
109
|
-
// Raw non-JSON output (cargo compile warnings, etc.)
|
|
110
|
-
console.log(line);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
child.stderr.on("data", (data) => {
|
|
116
|
-
const str = data.toString();
|
|
117
|
-
// Ignore normal compilation progress
|
|
118
|
-
if (!str.includes("Compiling") && !str.includes("Checking") && !str.includes("Finished")) {
|
|
119
|
-
reporter.addLog({
|
|
120
|
-
timestamp: new Date().toLocaleTimeString(),
|
|
121
|
-
level: "DEBUG",
|
|
122
|
-
message: str.trim(),
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
child.on("close", (code) => {
|
|
128
|
-
const result = reporter.generateResult(code === 0 ? "passed" : runStatus, runDuration);
|
|
129
|
-
|
|
130
|
-
if (options.outputJsonPath) {
|
|
131
|
-
const outDir = path.dirname(options.outputJsonPath);
|
|
132
|
-
if (!fs.existsSync(outDir)) {
|
|
133
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
134
|
-
}
|
|
135
|
-
fs.writeFileSync(options.outputJsonPath, JSON.stringify(result, null, 2), "utf-8");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
resolve(result);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
child.on("error", (err) => {
|
|
142
|
-
reject(err);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
package/src/runner/reporter.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
export interface TestLog {
|
|
2
|
-
timestamp: string;
|
|
3
|
-
level: "INFO" | "SUCCESS" | "WARNING" | "ERROR" | "DEBUG" | string;
|
|
4
|
-
message: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface NetworkResource {
|
|
8
|
-
requestId: string;
|
|
9
|
-
url: string;
|
|
10
|
-
method: string;
|
|
11
|
-
status: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface TestRunResult {
|
|
15
|
-
status: "passed" | "failed" | "error" | string;
|
|
16
|
-
duration: string;
|
|
17
|
-
logs: TestLog[];
|
|
18
|
-
networkResources?: NetworkResource[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class Reporter {
|
|
22
|
-
private logs: TestLog[] = [];
|
|
23
|
-
private networkEvents: NetworkResource[] = [];
|
|
24
|
-
|
|
25
|
-
addLog(log: TestLog) {
|
|
26
|
-
this.logs.push(log);
|
|
27
|
-
const time = `\x1b[90m${log.timestamp}\x1b[0m`;
|
|
28
|
-
let prefix = `[${log.level}]`;
|
|
29
|
-
if (log.level === "SUCCESS" || log.level === "PASSED") {
|
|
30
|
-
prefix = `\x1b[32m${prefix}\x1b[0m`;
|
|
31
|
-
} else if (log.level === "ERROR" || log.level === "FAILED") {
|
|
32
|
-
prefix = `\x1b[31m${prefix}\x1b[0m`;
|
|
33
|
-
} else if (log.level === "WARN" || log.level === "WARNING") {
|
|
34
|
-
prefix = `\x1b[33m${prefix}\x1b[0m`;
|
|
35
|
-
} else if (log.level === "INFO") {
|
|
36
|
-
prefix = `\x1b[36m${prefix}\x1b[0m`;
|
|
37
|
-
} else {
|
|
38
|
-
prefix = `\x1b[90m${prefix}\x1b[0m`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
console.log(`${time} ${prefix} ${log.message}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
addNetwork(res: NetworkResource) {
|
|
45
|
-
this.networkEvents.push(res);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
getLogs(): TestLog[] {
|
|
49
|
-
return this.logs;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
getNetworkEvents(): NetworkResource[] {
|
|
53
|
-
return this.networkEvents;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
generateResult(status: string, duration: string): TestRunResult {
|
|
57
|
-
return {
|
|
58
|
-
status,
|
|
59
|
-
duration,
|
|
60
|
-
logs: this.logs,
|
|
61
|
-
networkResources: this.networkEvents,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
}
|
package/src/step/index.ts
DELETED
package/src/step/matchers.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { SingleElementMatcher, MultiElementMatcher, ElementTarget } from "./types.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Resolves an ElementTarget (selector string, WebdriverIO.Element, or ChainablePromiseElement)
|
|
5
|
-
* to an actionable WebdriverIO.Element.
|
|
6
|
-
*/
|
|
7
|
-
export async function resolveElement(target: ElementTarget): Promise<WebdriverIO.Element> {
|
|
8
|
-
if (typeof target === "string") {
|
|
9
|
-
const el = await $(target);
|
|
10
|
-
return el as unknown as WebdriverIO.Element;
|
|
11
|
-
}
|
|
12
|
-
const el = await (target as any);
|
|
13
|
-
return el as unknown as WebdriverIO.Element;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Executes a single element matcher against the WebdriverIO browser / element context.
|
|
18
|
-
*/
|
|
19
|
-
export async function executeSingleMatcher(
|
|
20
|
-
target: ElementTarget | undefined,
|
|
21
|
-
matcher: SingleElementMatcher,
|
|
22
|
-
args: any[]
|
|
23
|
-
): Promise<void> {
|
|
24
|
-
const isBrowserLevel =
|
|
25
|
-
matcher === "have.url" ||
|
|
26
|
-
matcher === "contain.url" ||
|
|
27
|
-
matcher === "have.title" ||
|
|
28
|
-
matcher === "contain.title";
|
|
29
|
-
|
|
30
|
-
if (isBrowserLevel) {
|
|
31
|
-
const expected = args[0];
|
|
32
|
-
switch (matcher) {
|
|
33
|
-
case "have.url":
|
|
34
|
-
await expect(browser).toHaveUrl(expected);
|
|
35
|
-
break;
|
|
36
|
-
case "contain.url":
|
|
37
|
-
await expect(browser).toHaveUrl(expect.stringContaining(expected));
|
|
38
|
-
break;
|
|
39
|
-
case "have.title":
|
|
40
|
-
await expect(browser).toHaveTitle(expected);
|
|
41
|
-
break;
|
|
42
|
-
case "contain.title":
|
|
43
|
-
await expect(browser).toHaveTitle(expect.stringContaining(expected));
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (!target) {
|
|
50
|
-
throw new Error(`Assertion matcher '${matcher}' requires an element target.`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const el = await resolveElement(target);
|
|
54
|
-
|
|
55
|
-
switch (matcher) {
|
|
56
|
-
case "be.visible":
|
|
57
|
-
await expect(el).toBeDisplayed();
|
|
58
|
-
break;
|
|
59
|
-
case "not.be.visible":
|
|
60
|
-
await expect(el).not.toBeDisplayed();
|
|
61
|
-
break;
|
|
62
|
-
case "exist":
|
|
63
|
-
await expect(el).toExist();
|
|
64
|
-
break;
|
|
65
|
-
case "not.exist":
|
|
66
|
-
await expect(el).not.toExist();
|
|
67
|
-
break;
|
|
68
|
-
case "be.enabled":
|
|
69
|
-
await expect(el).toBeEnabled();
|
|
70
|
-
break;
|
|
71
|
-
case "be.disabled":
|
|
72
|
-
await expect(el).toBeDisabled();
|
|
73
|
-
break;
|
|
74
|
-
case "be.checked":
|
|
75
|
-
case "be.selected":
|
|
76
|
-
await expect(el).toBeSelected();
|
|
77
|
-
break;
|
|
78
|
-
case "have.value":
|
|
79
|
-
await expect(el).toHaveValue(args[0]);
|
|
80
|
-
break;
|
|
81
|
-
case "contain.value":
|
|
82
|
-
await expect(el).toHaveValue(expect.stringContaining(args[0]));
|
|
83
|
-
break;
|
|
84
|
-
case "have.text":
|
|
85
|
-
await expect(el).toHaveText(args[0]);
|
|
86
|
-
break;
|
|
87
|
-
case "contain.text":
|
|
88
|
-
await expect(el).toHaveText(expect.stringContaining(args[0]));
|
|
89
|
-
break;
|
|
90
|
-
case "have.class":
|
|
91
|
-
await expect(el).toHaveElementClass(args[0]);
|
|
92
|
-
break;
|
|
93
|
-
case "have.attr":
|
|
94
|
-
if (args.length >= 2) {
|
|
95
|
-
await expect(el).toHaveAttribute(args[0], args[1]);
|
|
96
|
-
} else {
|
|
97
|
-
await expect(el).toHaveAttribute(args[0]);
|
|
98
|
-
}
|
|
99
|
-
break;
|
|
100
|
-
default:
|
|
101
|
-
throw new Error(`Unsupported matcher: ${matcher}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Executes a multi-element collection matcher against WebdriverIO $$.
|
|
107
|
-
*/
|
|
108
|
-
export async function executeMultiMatcher(
|
|
109
|
-
selector: string,
|
|
110
|
-
matcher: MultiElementMatcher,
|
|
111
|
-
args: any[]
|
|
112
|
-
): Promise<void> {
|
|
113
|
-
const elements = await $$(selector);
|
|
114
|
-
|
|
115
|
-
switch (matcher) {
|
|
116
|
-
case "have.length":
|
|
117
|
-
await expect(elements).toBeElementsArrayOfSize(args[0]);
|
|
118
|
-
break;
|
|
119
|
-
case "have.length.greaterThan":
|
|
120
|
-
await expect(elements).toBeElementsArrayOfSize({ gte: args[0] + 1 });
|
|
121
|
-
break;
|
|
122
|
-
case "be.empty":
|
|
123
|
-
await expect(elements).toBeElementsArrayOfSize(0);
|
|
124
|
-
break;
|
|
125
|
-
case "exist":
|
|
126
|
-
await expect(elements).toBeElementsArrayOfSize({ gte: 1 });
|
|
127
|
-
break;
|
|
128
|
-
default:
|
|
129
|
-
throw new Error(`Unsupported multi-element matcher: ${matcher}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
package/src/step/proto.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { ElementTarget } from "./types.js";
|
|
2
|
-
import { resolveElement } from "./matchers.js";
|
|
3
|
-
|
|
4
|
-
declare global {
|
|
5
|
-
namespace WebdriverIO {
|
|
6
|
-
interface Element {
|
|
7
|
-
shouldBeVisible(): Promise<Element>;
|
|
8
|
-
shouldNotBeVisible(): Promise<Element>;
|
|
9
|
-
shouldExist(): Promise<Element>;
|
|
10
|
-
shouldNotExist(): Promise<Element>;
|
|
11
|
-
shouldBeEnabled(): Promise<Element>;
|
|
12
|
-
shouldBeDisabled(): Promise<Element>;
|
|
13
|
-
shouldBeSelected(): Promise<Element>;
|
|
14
|
-
shouldBeChecked(): Promise<Element>;
|
|
15
|
-
shouldHaveValue(expectedValue: string): Promise<Element>;
|
|
16
|
-
shouldContainValue(expectedValue: string): Promise<Element>;
|
|
17
|
-
shouldHaveText(expectedText: string): Promise<Element>;
|
|
18
|
-
shouldContainText(expectedText: string): Promise<Element>;
|
|
19
|
-
shouldHaveClass(className: string): Promise<Element>;
|
|
20
|
-
shouldHaveAttribute(attributeName: string, expectedValue?: string): Promise<Element>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface Browser {
|
|
24
|
-
shouldHaveUrl(expectedUrl: string): Promise<void>;
|
|
25
|
-
shouldContainUrl(expectedUrl: string): Promise<void>;
|
|
26
|
-
shouldHaveTitle(expectedTitle: string): Promise<void>;
|
|
27
|
-
shouldContainTitle(expectedTitle: string): Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface Promise<T> {
|
|
32
|
-
shouldBeVisible(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;
|
|
33
|
-
shouldNotBeVisible(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;
|
|
34
|
-
shouldExist(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;
|
|
35
|
-
shouldNotExist(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;
|
|
36
|
-
shouldBeEnabled(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;
|
|
37
|
-
shouldBeDisabled(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;
|
|
38
|
-
shouldBeSelected(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;
|
|
39
|
-
shouldBeChecked(this: Promise<WebdriverIO.Element>): Promise<WebdriverIO.Element>;
|
|
40
|
-
shouldHaveValue(this: Promise<WebdriverIO.Element>, expectedValue: string): Promise<WebdriverIO.Element>;
|
|
41
|
-
shouldContainValue(this: Promise<WebdriverIO.Element>, expectedValue: string): Promise<WebdriverIO.Element>;
|
|
42
|
-
shouldHaveText(this: Promise<WebdriverIO.Element>, expectedText: string): Promise<WebdriverIO.Element>;
|
|
43
|
-
shouldContainText(this: Promise<WebdriverIO.Element>, expectedText: string): Promise<WebdriverIO.Element>;
|
|
44
|
-
shouldHaveClass(this: Promise<WebdriverIO.Element>, className: string): Promise<WebdriverIO.Element>;
|
|
45
|
-
shouldHaveAttribute(this: Promise<WebdriverIO.Element>, attributeName: string, expectedValue?: string): Promise<WebdriverIO.Element>;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Install direct callable assertion methods onto WebdriverIO browser/element and Promise prototypes.
|
|
51
|
-
*/
|
|
52
|
-
export function installPrototypes() {
|
|
53
|
-
const elementCommands: Record<string, Function> = {
|
|
54
|
-
async shouldBeVisible(this: any) {
|
|
55
|
-
const el = await resolveElement(this);
|
|
56
|
-
await expect(el).toBeDisplayed();
|
|
57
|
-
return el;
|
|
58
|
-
},
|
|
59
|
-
async shouldNotBeVisible(this: any) {
|
|
60
|
-
const el = await resolveElement(this);
|
|
61
|
-
await expect(el).not.toBeDisplayed();
|
|
62
|
-
return el;
|
|
63
|
-
},
|
|
64
|
-
async shouldExist(this: any) {
|
|
65
|
-
const el = await resolveElement(this);
|
|
66
|
-
await expect(el).toExist();
|
|
67
|
-
return el;
|
|
68
|
-
},
|
|
69
|
-
async shouldNotExist(this: any) {
|
|
70
|
-
const el = await resolveElement(this);
|
|
71
|
-
await expect(el).not.toExist();
|
|
72
|
-
return el;
|
|
73
|
-
},
|
|
74
|
-
async shouldBeEnabled(this: any) {
|
|
75
|
-
const el = await resolveElement(this);
|
|
76
|
-
await expect(el).toBeEnabled();
|
|
77
|
-
return el;
|
|
78
|
-
},
|
|
79
|
-
async shouldBeDisabled(this: any) {
|
|
80
|
-
const el = await resolveElement(this);
|
|
81
|
-
await expect(el).toBeDisabled();
|
|
82
|
-
return el;
|
|
83
|
-
},
|
|
84
|
-
async shouldBeSelected(this: any) {
|
|
85
|
-
const el = await resolveElement(this);
|
|
86
|
-
await expect(el).toBeSelected();
|
|
87
|
-
return el;
|
|
88
|
-
},
|
|
89
|
-
async shouldBeChecked(this: any) {
|
|
90
|
-
const el = await resolveElement(this);
|
|
91
|
-
await expect(el).toBeSelected();
|
|
92
|
-
return el;
|
|
93
|
-
},
|
|
94
|
-
async shouldHaveValue(this: any, val: string) {
|
|
95
|
-
const el = await resolveElement(this);
|
|
96
|
-
await expect(el).toHaveValue(val);
|
|
97
|
-
return el;
|
|
98
|
-
},
|
|
99
|
-
async shouldContainValue(this: any, val: string) {
|
|
100
|
-
const el = await resolveElement(this);
|
|
101
|
-
await expect(el).toHaveValue(expect.stringContaining(val));
|
|
102
|
-
return el;
|
|
103
|
-
},
|
|
104
|
-
async shouldHaveText(this: any, text: string) {
|
|
105
|
-
const el = await resolveElement(this);
|
|
106
|
-
await expect(el).toHaveText(text);
|
|
107
|
-
return el;
|
|
108
|
-
},
|
|
109
|
-
async shouldContainText(this: any, text: string) {
|
|
110
|
-
const el = await resolveElement(this);
|
|
111
|
-
await expect(el).toHaveText(expect.stringContaining(text));
|
|
112
|
-
return el;
|
|
113
|
-
},
|
|
114
|
-
async shouldHaveClass(this: any, cls: string) {
|
|
115
|
-
const el = await resolveElement(this);
|
|
116
|
-
await expect(el).toHaveElementClass(cls);
|
|
117
|
-
return el;
|
|
118
|
-
},
|
|
119
|
-
async shouldHaveAttribute(this: any, name: string, val?: string) {
|
|
120
|
-
const el = await resolveElement(this);
|
|
121
|
-
if (val !== undefined) {
|
|
122
|
-
await expect(el).toHaveAttribute(name, val);
|
|
123
|
-
} else {
|
|
124
|
-
await expect(el).toHaveAttribute(name);
|
|
125
|
-
}
|
|
126
|
-
return el;
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const browserCommands: Record<string, Function> = {
|
|
131
|
-
async shouldHaveUrl(url: string) {
|
|
132
|
-
await expect(browser).toHaveUrl(url);
|
|
133
|
-
},
|
|
134
|
-
async shouldContainUrl(url: string) {
|
|
135
|
-
await expect(browser).toHaveUrl(expect.stringContaining(url));
|
|
136
|
-
},
|
|
137
|
-
async shouldHaveTitle(title: string) {
|
|
138
|
-
await expect(browser).toHaveTitle(title);
|
|
139
|
-
},
|
|
140
|
-
async shouldContainTitle(title: string) {
|
|
141
|
-
await expect(browser).toHaveTitle(expect.stringContaining(title));
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
if (typeof browser !== "undefined" && (browser as any).addCommand) {
|
|
146
|
-
for (const [name, fn] of Object.entries(elementCommands)) {
|
|
147
|
-
(browser as any).addCommand(name, fn, true);
|
|
148
|
-
}
|
|
149
|
-
for (const [name, fn] of Object.entries(browserCommands)) {
|
|
150
|
-
(browser as any).addCommand(name, fn, false);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Attach to Promise.prototype for ChainablePromiseElement unwrapping
|
|
155
|
-
for (const [name, fn] of Object.entries(elementCommands)) {
|
|
156
|
-
if (typeof (Promise.prototype as any)[name] !== "function") {
|
|
157
|
-
(Promise.prototype as any)[name] = async function (this: Promise<any>, ...args: any[]) {
|
|
158
|
-
const el = await this;
|
|
159
|
-
return await fn.call(el, ...args);
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { MultiElementMatcher } from "../types.js";
|
|
2
|
-
import { executeMultiMatcher } from "../matchers.js";
|
|
3
|
-
import { SingleElementRunner } from "./single.js";
|
|
4
|
-
|
|
5
|
-
type CollectionAssertion = {
|
|
6
|
-
matcher: MultiElementMatcher;
|
|
7
|
-
args: any[];
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export class MultiElementRunner implements PromiseLike<void> {
|
|
11
|
-
private _selector: string;
|
|
12
|
-
private _assertions: CollectionAssertion[] = [];
|
|
13
|
-
|
|
14
|
-
constructor(selector: string) {
|
|
15
|
-
this._selector = selector;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// --- Collection Assertions ---
|
|
19
|
-
|
|
20
|
-
should(matcher: MultiElementMatcher, ...args: any[]): this {
|
|
21
|
-
this._assertions.push({ matcher, args });
|
|
22
|
-
return this;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// --- Item Navigation ---
|
|
26
|
-
|
|
27
|
-
eq(index: number): SingleElementRunner {
|
|
28
|
-
const indexedSelector = `(${this._selector})[${index + 1}]`;
|
|
29
|
-
return new SingleElementRunner(indexedSelector);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
first(): SingleElementRunner {
|
|
33
|
-
return this.eq(0);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
last(): SingleElementRunner {
|
|
37
|
-
const indexedSelector = `(${this._selector})[last()]`;
|
|
38
|
-
return new SingleElementRunner(indexedSelector);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// --- Iteration Callback ---
|
|
42
|
-
|
|
43
|
-
async each(
|
|
44
|
-
callback: (el: SingleElementRunner, index: number) => Promise<void>
|
|
45
|
-
): Promise<void> {
|
|
46
|
-
const elements = await $$(this._selector);
|
|
47
|
-
const count = await (elements as any).length;
|
|
48
|
-
for (let i = 0; i < count; i++) {
|
|
49
|
-
const runner = this.eq(i);
|
|
50
|
-
await callback(runner, i);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// --- Execution when awaited ---
|
|
55
|
-
|
|
56
|
-
async then<TResult1 = void, TResult2 = never>(
|
|
57
|
-
onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null,
|
|
58
|
-
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
59
|
-
): Promise<TResult1 | TResult2> {
|
|
60
|
-
try {
|
|
61
|
-
for (const assert of this._assertions) {
|
|
62
|
-
await executeMultiMatcher(this._selector, assert.matcher, assert.args);
|
|
63
|
-
}
|
|
64
|
-
const res = (onfulfilled ? await onfulfilled() : undefined) as TResult1;
|
|
65
|
-
return res;
|
|
66
|
-
} catch (err) {
|
|
67
|
-
if (onrejected) {
|
|
68
|
-
return onrejected(err);
|
|
69
|
-
}
|
|
70
|
-
throw err;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|