@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/commands/init.ts
DELETED
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
|
-
import { ConfigLoader } from "../config/loader.js";
|
|
5
|
-
import { TypeGenerator } from "../types/generator.js";
|
|
6
|
-
|
|
7
|
-
export async function initCommand(options: { force?: boolean }) {
|
|
8
|
-
const cwd = process.cwd();
|
|
9
|
-
const existingConfig = ConfigLoader.findConfigFile(cwd);
|
|
10
|
-
|
|
11
|
-
if (existingConfig && !options.force) {
|
|
12
|
-
console.log(`\x1b[33m[TestSpectra]\x1b[0m Config already exists at ${path.basename(existingConfig)}. Use --force to overwrite.`);
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// 1. Ensure directories exist
|
|
17
|
-
const dirs = [
|
|
18
|
-
"specs/TC-LOGIN-01",
|
|
19
|
-
"page-objects/LoginPage",
|
|
20
|
-
"actions/verifyOtp",
|
|
21
|
-
"steps/loginUser",
|
|
22
|
-
"fixtures",
|
|
23
|
-
"hooks/default",
|
|
24
|
-
".testspectra",
|
|
25
|
-
];
|
|
26
|
-
for (const d of dirs) {
|
|
27
|
-
const dirPath = path.join(cwd, d);
|
|
28
|
-
if (!fs.existsSync(dirPath)) {
|
|
29
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 2. Create spectra.config.ts
|
|
34
|
-
const configContent = `import { defineConfig } from "@testspectra/cli";
|
|
35
|
-
|
|
36
|
-
export default defineConfig({
|
|
37
|
-
webConfig: {
|
|
38
|
-
baseUrl: "https://the-internet.herokuapp.com",
|
|
39
|
-
maxConcurrentSessions: "1",
|
|
40
|
-
headlessMode: true,
|
|
41
|
-
implicitWait: "5000",
|
|
42
|
-
pageLoadTimeout: "30000",
|
|
43
|
-
scriptTimeout: "30000",
|
|
44
|
-
parallelizationMode: "testcase",
|
|
45
|
-
},
|
|
46
|
-
browsers: [
|
|
47
|
-
{
|
|
48
|
-
id: "chrome-desktop",
|
|
49
|
-
type: "chrome",
|
|
50
|
-
mobileEmulation: false,
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
androidConfig: {
|
|
54
|
-
appiumServer: "http://127.0.0.1:4723",
|
|
55
|
-
platformName: "Android",
|
|
56
|
-
platformVersion: "13",
|
|
57
|
-
deviceName: "emulator-5554",
|
|
58
|
-
automationName: "UiAutomator2",
|
|
59
|
-
appPackage: "",
|
|
60
|
-
appActivity: "",
|
|
61
|
-
autoGrantPermissions: true,
|
|
62
|
-
noReset: false,
|
|
63
|
-
implicitWait: "10000",
|
|
64
|
-
parallelizationMode: "suite",
|
|
65
|
-
},
|
|
66
|
-
iosConfig: {
|
|
67
|
-
appiumServer: "http://127.0.0.1:4723",
|
|
68
|
-
platformName: "iOS",
|
|
69
|
-
platformVersion: "16.0",
|
|
70
|
-
deviceName: "iPhone 14",
|
|
71
|
-
automationName: "XCUITest",
|
|
72
|
-
bundleId: "",
|
|
73
|
-
udid: "auto",
|
|
74
|
-
xcodeOrgId: "",
|
|
75
|
-
xcodeSigningId: "iPhone Developer",
|
|
76
|
-
autoAcceptAlerts: true,
|
|
77
|
-
noReset: false,
|
|
78
|
-
implicitWait: "10000",
|
|
79
|
-
parallelizationMode: "suite",
|
|
80
|
-
},
|
|
81
|
-
loadConfig: {
|
|
82
|
-
virtualUsers: "10",
|
|
83
|
-
duration: "1m",
|
|
84
|
-
},
|
|
85
|
-
loadStages: [],
|
|
86
|
-
thresholds: [],
|
|
87
|
-
executionConfig: {
|
|
88
|
-
networkMonitoringEnabled: true,
|
|
89
|
-
fastResponseTime: "200",
|
|
90
|
-
normalResponseTime: "1000",
|
|
91
|
-
monitoredDomains: [],
|
|
92
|
-
environmentVariables: [],
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
`;
|
|
96
|
-
fs.writeFileSync(path.join(cwd, "spectra.config.ts"), configContent, "utf-8");
|
|
97
|
-
console.log(`\x1b[32m[TestSpectra]\x1b[0m Initialized spectra.config.ts with defineConfig`);
|
|
98
|
-
|
|
99
|
-
// 3. Create or update package.json with npm scripts & devDependencies
|
|
100
|
-
const packageJsonPath = path.join(cwd, "package.json");
|
|
101
|
-
|
|
102
|
-
// Dynamically resolve CLI version
|
|
103
|
-
let cliVersion = "^1.0.6";
|
|
104
|
-
try {
|
|
105
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
106
|
-
const __dirname = path.dirname(__filename);
|
|
107
|
-
const cliPackageJsonPath = path.resolve(__dirname, "../../package.json");
|
|
108
|
-
if (fs.existsSync(cliPackageJsonPath)) {
|
|
109
|
-
const cliPkg = JSON.parse(fs.readFileSync(cliPackageJsonPath, "utf-8"));
|
|
110
|
-
if (cliPkg.version) {
|
|
111
|
-
cliVersion = `^${cliPkg.version}`;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
} catch {}
|
|
115
|
-
|
|
116
|
-
// Check if cwd is inside the testspectra monorepo with a pnpm-workspace.yaml
|
|
117
|
-
let isInternalWorkspace = false;
|
|
118
|
-
let cur = cwd;
|
|
119
|
-
while (cur !== path.dirname(cur)) {
|
|
120
|
-
if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml"))) {
|
|
121
|
-
try {
|
|
122
|
-
const wsContent = fs.readFileSync(path.join(cur, "pnpm-workspace.yaml"), "utf-8");
|
|
123
|
-
if (wsContent.includes("cli")) {
|
|
124
|
-
isInternalWorkspace = true;
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
} catch {}
|
|
128
|
-
}
|
|
129
|
-
cur = path.dirname(cur);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const cliDepVersion = isInternalWorkspace ? "workspace:*" : cliVersion;
|
|
133
|
-
|
|
134
|
-
let pkg: any = {
|
|
135
|
-
name: path.basename(cwd),
|
|
136
|
-
version: "1.0.0",
|
|
137
|
-
private: true,
|
|
138
|
-
type: "module",
|
|
139
|
-
scripts: {
|
|
140
|
-
test: "spectra run",
|
|
141
|
-
"type-check": "tsc -b",
|
|
142
|
-
},
|
|
143
|
-
devDependencies: {
|
|
144
|
-
"@testspectra/cli": cliDepVersion,
|
|
145
|
-
"@types/node": "^20.14.0",
|
|
146
|
-
"@wdio/globals": "^9.2.8",
|
|
147
|
-
"@wdio/mocha-framework": "^9.2.8",
|
|
148
|
-
"webdriverio": "^9.2.8",
|
|
149
|
-
"typescript": "^5.4.5",
|
|
150
|
-
},
|
|
151
|
-
};
|
|
152
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
153
|
-
try {
|
|
154
|
-
const existing = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
155
|
-
pkg = {
|
|
156
|
-
...existing,
|
|
157
|
-
scripts: {
|
|
158
|
-
...(existing.scripts || {}),
|
|
159
|
-
test: existing.scripts?.test || "spectra run",
|
|
160
|
-
"type-check": existing.scripts?.["type-check"] || "tsc -b",
|
|
161
|
-
},
|
|
162
|
-
devDependencies: {
|
|
163
|
-
...(existing.devDependencies || {}),
|
|
164
|
-
"@testspectra/cli": existing.devDependencies?.["@testspectra/cli"]?.startsWith("workspace:") && !isInternalWorkspace
|
|
165
|
-
? cliVersion
|
|
166
|
-
: existing.devDependencies?.["@testspectra/cli"] || cliDepVersion,
|
|
167
|
-
"@types/node": existing.devDependencies?.["@types/node"] || "^20.14.0",
|
|
168
|
-
"@wdio/globals": existing.devDependencies?.["@wdio/globals"] || "^9.2.8",
|
|
169
|
-
"@wdio/mocha-framework": existing.devDependencies?.["@wdio/mocha-framework"] || "^9.2.8",
|
|
170
|
-
"webdriverio": existing.devDependencies?.["webdriverio"] || "^9.2.8",
|
|
171
|
-
"typescript": existing.devDependencies?.["typescript"] || "^5.4.5",
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
} catch {}
|
|
175
|
-
}
|
|
176
|
-
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), "utf-8");
|
|
177
|
-
console.log(`\x1b[32m[TestSpectra]\x1b[0m Updated package.json (scripts & devDependencies)`);
|
|
178
|
-
|
|
179
|
-
// 3b. Create .gitignore
|
|
180
|
-
const gitignorePath = path.join(cwd, ".gitignore");
|
|
181
|
-
if (!fs.existsSync(gitignorePath) || options.force) {
|
|
182
|
-
const gitignoreContent = `node_modules/\ndist/\n.testspectra/\n*.tsbuildinfo\n`;
|
|
183
|
-
fs.writeFileSync(gitignorePath, gitignoreContent, "utf-8");
|
|
184
|
-
console.log(`\x1b[32m[TestSpectra]\x1b[0m Created .gitignore`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// 4. Create platform tsconfigs with Solution-Style Project References & TS Plugin
|
|
188
|
-
const tsconfigRoot = {
|
|
189
|
-
compilerOptions: {
|
|
190
|
-
target: "ES2022",
|
|
191
|
-
module: "NodeNext",
|
|
192
|
-
moduleResolution: "NodeNext",
|
|
193
|
-
types: ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
|
|
194
|
-
plugins: [
|
|
195
|
-
{ name: "@testspectra/cli" }
|
|
196
|
-
],
|
|
197
|
-
skipLibCheck: true,
|
|
198
|
-
strict: true,
|
|
199
|
-
noEmit: true,
|
|
200
|
-
},
|
|
201
|
-
files: ["spectra.config.ts"],
|
|
202
|
-
references: [
|
|
203
|
-
{ path: "./tsconfig.web.json" },
|
|
204
|
-
{ path: "./tsconfig.android.json" },
|
|
205
|
-
{ path: "./tsconfig.ios.json" },
|
|
206
|
-
],
|
|
207
|
-
};
|
|
208
|
-
fs.writeFileSync(path.join(cwd, "tsconfig.json"), JSON.stringify(tsconfigRoot, null, 2), "utf-8");
|
|
209
|
-
|
|
210
|
-
const tsconfigWeb = {
|
|
211
|
-
compilerOptions: {
|
|
212
|
-
target: "ES2022",
|
|
213
|
-
module: "NodeNext",
|
|
214
|
-
moduleResolution: "NodeNext",
|
|
215
|
-
types: ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
|
|
216
|
-
skipLibCheck: true,
|
|
217
|
-
strict: true,
|
|
218
|
-
composite: true,
|
|
219
|
-
emitDeclarationOnly: true,
|
|
220
|
-
outDir: "./.testspectra/.build/web",
|
|
221
|
-
},
|
|
222
|
-
include: [
|
|
223
|
-
"specs/**/web.test.ts",
|
|
224
|
-
"specs/**/common.test.ts",
|
|
225
|
-
"page-objects/**/web.ts",
|
|
226
|
-
"page-objects/**/common.ts",
|
|
227
|
-
"actions/**/web.action.ts",
|
|
228
|
-
"actions/**/common.action.ts",
|
|
229
|
-
"steps/**/web.step.ts",
|
|
230
|
-
"steps/**/common.step.ts",
|
|
231
|
-
"hooks/**/before.web.hook.ts",
|
|
232
|
-
"hooks/**/before.hook.ts",
|
|
233
|
-
".testspectra/types/web.d.ts",
|
|
234
|
-
".testspectra/types/common.d.ts",
|
|
235
|
-
".testspectra/types/fixtures.d.ts",
|
|
236
|
-
],
|
|
237
|
-
};
|
|
238
|
-
fs.writeFileSync(path.join(cwd, "tsconfig.web.json"), JSON.stringify(tsconfigWeb, null, 2), "utf-8");
|
|
239
|
-
|
|
240
|
-
const tsconfigAndroid = {
|
|
241
|
-
compilerOptions: {
|
|
242
|
-
target: "ES2022",
|
|
243
|
-
module: "NodeNext",
|
|
244
|
-
moduleResolution: "NodeNext",
|
|
245
|
-
types: ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
|
|
246
|
-
skipLibCheck: true,
|
|
247
|
-
strict: true,
|
|
248
|
-
composite: true,
|
|
249
|
-
emitDeclarationOnly: true,
|
|
250
|
-
outDir: "./.testspectra/.build/android",
|
|
251
|
-
},
|
|
252
|
-
include: [
|
|
253
|
-
"specs/**/android.test.ts",
|
|
254
|
-
"specs/**/mobile.test.ts",
|
|
255
|
-
"specs/**/common.test.ts",
|
|
256
|
-
"page-objects/**/android.ts",
|
|
257
|
-
"page-objects/**/mobile.ts",
|
|
258
|
-
"page-objects/**/common.ts",
|
|
259
|
-
"actions/**/android.action.ts",
|
|
260
|
-
"actions/**/mobile.action.ts",
|
|
261
|
-
"actions/**/common.action.ts",
|
|
262
|
-
"steps/**/android.step.ts",
|
|
263
|
-
"steps/**/mobile.step.ts",
|
|
264
|
-
"steps/**/common.step.ts",
|
|
265
|
-
"hooks/**/before.android.hook.ts",
|
|
266
|
-
"hooks/**/before.mobile.hook.ts",
|
|
267
|
-
"hooks/**/before.hook.ts",
|
|
268
|
-
".testspectra/types/android.d.ts",
|
|
269
|
-
".testspectra/types/mobile.d.ts",
|
|
270
|
-
".testspectra/types/common.d.ts",
|
|
271
|
-
".testspectra/types/fixtures.d.ts",
|
|
272
|
-
],
|
|
273
|
-
};
|
|
274
|
-
fs.writeFileSync(path.join(cwd, "tsconfig.android.json"), JSON.stringify(tsconfigAndroid, null, 2), "utf-8");
|
|
275
|
-
|
|
276
|
-
const tsconfigIos = {
|
|
277
|
-
compilerOptions: {
|
|
278
|
-
target: "ES2022",
|
|
279
|
-
module: "NodeNext",
|
|
280
|
-
moduleResolution: "NodeNext",
|
|
281
|
-
types: ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
|
|
282
|
-
skipLibCheck: true,
|
|
283
|
-
strict: true,
|
|
284
|
-
composite: true,
|
|
285
|
-
emitDeclarationOnly: true,
|
|
286
|
-
outDir: "./.testspectra/.build/ios",
|
|
287
|
-
},
|
|
288
|
-
include: [
|
|
289
|
-
"specs/**/ios.test.ts",
|
|
290
|
-
"specs/**/mobile.test.ts",
|
|
291
|
-
"specs/**/common.test.ts",
|
|
292
|
-
"page-objects/**/ios.ts",
|
|
293
|
-
"page-objects/**/mobile.ts",
|
|
294
|
-
"page-objects/**/common.ts",
|
|
295
|
-
"actions/**/ios.action.ts",
|
|
296
|
-
"actions/**/mobile.action.ts",
|
|
297
|
-
"actions/**/common.action.ts",
|
|
298
|
-
"steps/**/ios.step.ts",
|
|
299
|
-
"steps/**/mobile.step.ts",
|
|
300
|
-
"steps/**/common.step.ts",
|
|
301
|
-
"hooks/**/before.ios.hook.ts",
|
|
302
|
-
"hooks/**/before.mobile.hook.ts",
|
|
303
|
-
"hooks/**/before.hook.ts",
|
|
304
|
-
".testspectra/types/ios.d.ts",
|
|
305
|
-
".testspectra/types/mobile.d.ts",
|
|
306
|
-
".testspectra/types/common.d.ts",
|
|
307
|
-
".testspectra/types/fixtures.d.ts",
|
|
308
|
-
],
|
|
309
|
-
};
|
|
310
|
-
fs.writeFileSync(path.join(cwd, "tsconfig.ios.json"), JSON.stringify(tsconfigIos, null, 2), "utf-8");
|
|
311
|
-
console.log(`\x1b[32m[TestSpectra]\x1b[0m Created platform-isolated tsconfigs with solution references`);
|
|
312
|
-
|
|
313
|
-
// 5. Scaffold Fixture File
|
|
314
|
-
const fixturePath = path.join(cwd, "fixtures", "userData.json");
|
|
315
|
-
if (!fs.existsSync(fixturePath) || options.force) {
|
|
316
|
-
fs.writeFileSync(fixturePath, JSON.stringify({ username: "tomsmith", role: "admin" }, null, 2), "utf-8");
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// 6. Scaffold Page Objects (Clean zero-import methods with explicit ChainablePromiseElement types)
|
|
320
|
-
const poWeb = `export default class LoginPage {
|
|
321
|
-
static get usernameInput(): ChainablePromiseElement {
|
|
322
|
-
return $('#username');
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
static get passwordInput(): ChainablePromiseElement {
|
|
326
|
-
return $('#password');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
static get submitButton(): ChainablePromiseElement {
|
|
330
|
-
return $('button[type="submit"]');
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
static get flashAlert(): ChainablePromiseElement {
|
|
334
|
-
return $('#flash');
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
static async open() {
|
|
338
|
-
await Spectra.navigate('/login');
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
static async login(username: string, pass: string) {
|
|
342
|
-
await Spectra.type(this.usernameInput, username);
|
|
343
|
-
await Spectra.type(this.passwordInput, pass);
|
|
344
|
-
await Spectra.click(this.submitButton);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
`;
|
|
348
|
-
fs.writeFileSync(path.join(cwd, "page-objects/LoginPage/web.ts"), poWeb, "utf-8");
|
|
349
|
-
|
|
350
|
-
const poMobile = `export default class LoginPage {
|
|
351
|
-
static get usernameInput(): ChainablePromiseElement {
|
|
352
|
-
return $('~username_input');
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
static get passwordInput(): ChainablePromiseElement {
|
|
356
|
-
return $('~password_input');
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
static get submitButton(): ChainablePromiseElement {
|
|
360
|
-
return $('~login_button');
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
static get welcomeText(): ChainablePromiseElement {
|
|
364
|
-
return $('~welcome_text');
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
static async open() {
|
|
368
|
-
// Mobile app startup
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
static async login(username: string, pass: string) {
|
|
372
|
-
await Spectra.type(this.usernameInput, username);
|
|
373
|
-
await Spectra.type(this.passwordInput, pass);
|
|
374
|
-
await Spectra.click(this.submitButton);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
`;
|
|
378
|
-
fs.writeFileSync(path.join(cwd, "page-objects/LoginPage/mobile.ts"), poMobile, "utf-8");
|
|
379
|
-
|
|
380
|
-
// 7. Scaffold Actions (Zero triple-slash lines)
|
|
381
|
-
const actionWeb = `export default async function verifyOtp(otp: string): Promise<void> {
|
|
382
|
-
const otpInput = await $('#otp');
|
|
383
|
-
await otpInput.setValue(otp);
|
|
384
|
-
const verifyBtn = await $('#verify-btn');
|
|
385
|
-
await verifyBtn.click();
|
|
386
|
-
}
|
|
387
|
-
`;
|
|
388
|
-
fs.writeFileSync(path.join(cwd, "actions/verifyOtp/web.action.ts"), actionWeb, "utf-8");
|
|
389
|
-
|
|
390
|
-
const actionMobile = `export default async function verifyOtp(otp: string): Promise<void> {
|
|
391
|
-
const otpInput = await $('~otp_input');
|
|
392
|
-
await otpInput.setValue(otp);
|
|
393
|
-
const verifyBtn = await $('~verify_btn');
|
|
394
|
-
await verifyBtn.click();
|
|
395
|
-
}
|
|
396
|
-
`;
|
|
397
|
-
fs.writeFileSync(path.join(cwd, "actions/verifyOtp/mobile.action.ts"), actionMobile, "utf-8");
|
|
398
|
-
|
|
399
|
-
// 8. Scaffold Steps (Zero triple-slash lines)
|
|
400
|
-
const stepWeb = `export default async function loginUser(u: string, p: string): Promise<void> {
|
|
401
|
-
await LoginPage.open();
|
|
402
|
-
await LoginPage.login(u, p);
|
|
403
|
-
}
|
|
404
|
-
`;
|
|
405
|
-
fs.writeFileSync(path.join(cwd, "steps/loginUser/web.step.ts"), stepWeb, "utf-8");
|
|
406
|
-
|
|
407
|
-
const stepMobile = `export default async function loginUser(u: string, p: string): Promise<void> {
|
|
408
|
-
await LoginPage.open();
|
|
409
|
-
await LoginPage.login(u, p);
|
|
410
|
-
}
|
|
411
|
-
`;
|
|
412
|
-
fs.writeFileSync(path.join(cwd, "steps/loginUser/mobile.step.ts"), stepMobile, "utf-8");
|
|
413
|
-
|
|
414
|
-
// 9. Scaffold Hooks (Zero triple-slash lines)
|
|
415
|
-
const hookWeb = `export default async function (): Promise<void> {
|
|
416
|
-
await browser.maximizeWindow();
|
|
417
|
-
}
|
|
418
|
-
`;
|
|
419
|
-
fs.writeFileSync(path.join(cwd, "hooks/default/before.web.hook.ts"), hookWeb, "utf-8");
|
|
420
|
-
|
|
421
|
-
const hookAndroid = `export default async function (): Promise<void> {
|
|
422
|
-
// Setup Android Appium capabilities
|
|
423
|
-
}
|
|
424
|
-
`;
|
|
425
|
-
fs.writeFileSync(path.join(cwd, "hooks/default/before.android.hook.ts"), hookAndroid, "utf-8");
|
|
426
|
-
|
|
427
|
-
const hookIos = `export default async function (): Promise<void> {
|
|
428
|
-
// Setup iOS Appium capabilities
|
|
429
|
-
}
|
|
430
|
-
`;
|
|
431
|
-
fs.writeFileSync(path.join(cwd, "hooks/default/before.ios.hook.ts"), hookIos, "utf-8");
|
|
432
|
-
|
|
433
|
-
// 10. Scaffold Specs (Pure test scripts, ZERO triple-slash lines, ZERO raw selectors)
|
|
434
|
-
const specWeb = `it("should authenticate user using Page Objects and Spectra assertions", async () => {
|
|
435
|
-
await browser.intercept("/api/status", "GET", Fixture.userData);
|
|
436
|
-
|
|
437
|
-
// 1. Navigation via Page Object
|
|
438
|
-
await LoginPage.open();
|
|
439
|
-
|
|
440
|
-
// 2. High-level business flow via Shared Step & Page Object
|
|
441
|
-
await Step.loginUser("tomsmith", "SuperSecretPassword!");
|
|
442
|
-
|
|
443
|
-
// 3. Direct callable assertion on Page Object element
|
|
444
|
-
await LoginPage.flashAlert.shouldBeVisible();
|
|
445
|
-
await LoginPage.flashAlert.shouldContainText("You logged into a secure area!");
|
|
446
|
-
});
|
|
447
|
-
`;
|
|
448
|
-
fs.writeFileSync(path.join(cwd, "specs/TC-LOGIN-01/web.test.ts"), specWeb, "utf-8");
|
|
449
|
-
|
|
450
|
-
const specAndroid = `it("should authenticate user on Android Appium device using Page Objects", async () => {
|
|
451
|
-
await Step.loginUser("tomsmith", "SuperSecretPassword!");
|
|
452
|
-
|
|
453
|
-
await LoginPage.welcomeText.shouldBeVisible();
|
|
454
|
-
});
|
|
455
|
-
`;
|
|
456
|
-
fs.writeFileSync(path.join(cwd, "specs/TC-LOGIN-01/android.test.ts"), specAndroid, "utf-8");
|
|
457
|
-
|
|
458
|
-
const specIos = `it("should authenticate user on iOS Appium device using Page Objects", async () => {
|
|
459
|
-
await Step.loginUser("tomsmith", "SuperSecretPassword!");
|
|
460
|
-
|
|
461
|
-
await LoginPage.welcomeText.shouldBeVisible();
|
|
462
|
-
});
|
|
463
|
-
`;
|
|
464
|
-
fs.writeFileSync(path.join(cwd, "specs/TC-LOGIN-01/ios.test.ts"), specIos, "utf-8");
|
|
465
|
-
|
|
466
|
-
// 11. Generate ambient declaration files
|
|
467
|
-
TypeGenerator.writeDeclarationFiles(cwd);
|
|
468
|
-
console.log(`\x1b[32m[TestSpectra]\x1b[0m Generated ambient multi-platform types in .testspectra/types/`);
|
|
469
|
-
console.log(`\x1b[32m[TestSpectra]\x1b[0m Project initialized successfully!`);
|
|
470
|
-
}
|
package/src/commands/run.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { ConfigLoader } from "../config/loader.js";
|
|
4
|
-
import { RustCoreBridge } from "../runner/bridge.js";
|
|
5
|
-
import { Reporter } from "../runner/reporter.js";
|
|
6
|
-
import { TypeGenerator } from "../types/generator.js";
|
|
7
|
-
|
|
8
|
-
export interface RunCommandOptions {
|
|
9
|
-
target?: "web" | "android" | "ios" | "common";
|
|
10
|
-
device?: string;
|
|
11
|
-
headless?: boolean;
|
|
12
|
-
workdir?: string;
|
|
13
|
-
output?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function runCommand(specPath?: string, options: RunCommandOptions = {}) {
|
|
17
|
-
const cwd = process.cwd();
|
|
18
|
-
|
|
19
|
-
// Ensure latest ambient types are up-to-date
|
|
20
|
-
TypeGenerator.writeDeclarationFiles(cwd);
|
|
21
|
-
|
|
22
|
-
const config = await ConfigLoader.loadConfig(cwd);
|
|
23
|
-
|
|
24
|
-
if (options.headless !== undefined) {
|
|
25
|
-
config.webConfig.headlessMode = options.headless;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const platform = options.target || "web";
|
|
29
|
-
const appDataPath = options.workdir ? path.resolve(options.workdir) : path.join(cwd, ".testspectra");
|
|
30
|
-
|
|
31
|
-
if (!fs.existsSync(appDataPath)) {
|
|
32
|
-
fs.mkdirSync(appDataPath, { recursive: true });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Determine suite/test case name
|
|
36
|
-
let suiteName = "default";
|
|
37
|
-
let testCases: Array<{ id: string; title: string; executionOrder?: number }> = [];
|
|
38
|
-
|
|
39
|
-
if (specPath) {
|
|
40
|
-
const parsed = path.parse(specPath);
|
|
41
|
-
const baseName = parsed.name.replace(/\.(web|android|ios|common|test|spec)/g, "");
|
|
42
|
-
suiteName = baseName;
|
|
43
|
-
testCases.push({
|
|
44
|
-
id: baseName,
|
|
45
|
-
title: baseName,
|
|
46
|
-
executionOrder: 1,
|
|
47
|
-
});
|
|
48
|
-
} else {
|
|
49
|
-
// Scan specs/ directory (both entity folders specs/TC-001/web.test.ts and flat files)
|
|
50
|
-
const specsDir = path.join(cwd, "specs");
|
|
51
|
-
if (fs.existsSync(specsDir)) {
|
|
52
|
-
const entries = fs.readdirSync(specsDir, { withFileTypes: true });
|
|
53
|
-
for (const entry of entries) {
|
|
54
|
-
if (entry.name.startsWith(".")) continue;
|
|
55
|
-
if (entry.isDirectory()) {
|
|
56
|
-
testCases.push({ id: entry.name, title: entry.name });
|
|
57
|
-
} else if (entry.isFile() && (entry.name.endsWith(".test.ts") || entry.name.endsWith(".spec.ts"))) {
|
|
58
|
-
const id = entry.name.replace(/\.(web|android|ios|common|all|mobile|test|spec)\.ts$/g, "").replace(/\.test\.ts$/, "");
|
|
59
|
-
testCases.push({ id, title: id });
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (testCases.length === 0) {
|
|
66
|
-
testCases.push({
|
|
67
|
-
id: suiteName,
|
|
68
|
-
title: suiteName,
|
|
69
|
-
executionOrder: 1,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const outputJsonPath = options.output
|
|
74
|
-
? path.resolve(options.output)
|
|
75
|
-
: path.join(appDataPath, "reports/result.json");
|
|
76
|
-
|
|
77
|
-
console.log(`\x1b[36m[TestSpectra]\x1b[0m Starting execution on target \x1b[1m${platform}\x1b[0m...`);
|
|
78
|
-
console.log(`\x1b[36m[TestSpectra]\x1b[0m Suite: ${suiteName} (${testCases.length} case(s))\n`);
|
|
79
|
-
|
|
80
|
-
const reporter = new Reporter();
|
|
81
|
-
try {
|
|
82
|
-
const result = await RustCoreBridge.run(
|
|
83
|
-
{
|
|
84
|
-
baseDir: cwd,
|
|
85
|
-
appDataPath,
|
|
86
|
-
platform,
|
|
87
|
-
suite: suiteName,
|
|
88
|
-
testCases,
|
|
89
|
-
config,
|
|
90
|
-
targetDevice: options.device,
|
|
91
|
-
outputJsonPath,
|
|
92
|
-
},
|
|
93
|
-
reporter
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
console.log(`\n\x1b[36m[TestSpectra]\x1b[0m Run finished: \x1b[1m${result.status.toUpperCase()}\x1b[0m in ${result.duration}`);
|
|
97
|
-
console.log(`\x1b[36m[TestSpectra]\x1b[0m Report saved to: ${outputJsonPath}`);
|
|
98
|
-
} catch (e) {
|
|
99
|
-
console.error(`\x1b[31m[TestSpectra] Execution error:\x1b[0m`, e);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
|
-
}
|
package/src/config/loader.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { ConfigData, DEFAULT_CONFIG_DATA } from "./schema.js";
|
|
4
|
-
|
|
5
|
-
export class ConfigLoader {
|
|
6
|
-
static readonly CONFIG_FILE_NAMES = [
|
|
7
|
-
"testspectra.config.ts",
|
|
8
|
-
"spectra.config.ts",
|
|
9
|
-
"testspectra.config.js",
|
|
10
|
-
"spectra.config.js",
|
|
11
|
-
"testspectra.config.json",
|
|
12
|
-
".testspectrarc.json",
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
static findConfigFile(cwd: string = process.cwd()): string | null {
|
|
16
|
-
for (const filename of this.CONFIG_FILE_NAMES) {
|
|
17
|
-
const fullPath = path.join(cwd, filename);
|
|
18
|
-
if (fs.existsSync(fullPath)) {
|
|
19
|
-
return fullPath;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
static async loadConfig(cwd: string = process.cwd(), overrides?: Partial<ConfigData>): Promise<ConfigData> {
|
|
26
|
-
const configPath = this.findConfigFile(cwd);
|
|
27
|
-
let loaded: Partial<ConfigData> = {};
|
|
28
|
-
|
|
29
|
-
if (configPath) {
|
|
30
|
-
if (configPath.endsWith(".json")) {
|
|
31
|
-
try {
|
|
32
|
-
const raw = fs.readFileSync(configPath, "utf-8");
|
|
33
|
-
loaded = JSON.parse(raw);
|
|
34
|
-
} catch (e) {
|
|
35
|
-
console.warn(`[TestSpectra] Warning: Failed to parse ${configPath}:`, e);
|
|
36
|
-
}
|
|
37
|
-
} else if (configPath.endsWith(".ts") || configPath.endsWith(".js")) {
|
|
38
|
-
try {
|
|
39
|
-
const fileUrl = new URL(`file://${path.resolve(configPath)}`).href;
|
|
40
|
-
const mod = await import(fileUrl);
|
|
41
|
-
loaded = mod.default || mod.config || mod;
|
|
42
|
-
} catch (e) {
|
|
43
|
-
console.warn(`[TestSpectra] Warning: Failed to import ${configPath}:`, e);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
...DEFAULT_CONFIG_DATA,
|
|
50
|
-
...loaded,
|
|
51
|
-
...overrides,
|
|
52
|
-
webConfig: {
|
|
53
|
-
...DEFAULT_CONFIG_DATA.webConfig,
|
|
54
|
-
...(loaded.webConfig || {}),
|
|
55
|
-
...(overrides?.webConfig || {}),
|
|
56
|
-
},
|
|
57
|
-
androidConfig: {
|
|
58
|
-
...DEFAULT_CONFIG_DATA.androidConfig,
|
|
59
|
-
...(loaded.androidConfig || {}),
|
|
60
|
-
...(overrides?.androidConfig || {}),
|
|
61
|
-
},
|
|
62
|
-
iosConfig: {
|
|
63
|
-
...DEFAULT_CONFIG_DATA.iosConfig,
|
|
64
|
-
...(loaded.iosConfig || {}),
|
|
65
|
-
...(overrides?.iosConfig || {}),
|
|
66
|
-
},
|
|
67
|
-
executionConfig: {
|
|
68
|
-
networkMonitoringEnabled: overrides?.executionConfig?.networkMonitoringEnabled ?? loaded.executionConfig?.networkMonitoringEnabled ?? DEFAULT_CONFIG_DATA.executionConfig?.networkMonitoringEnabled ?? true,
|
|
69
|
-
fastResponseTime: overrides?.executionConfig?.fastResponseTime ?? loaded.executionConfig?.fastResponseTime ?? DEFAULT_CONFIG_DATA.executionConfig?.fastResponseTime ?? "200",
|
|
70
|
-
normalResponseTime: overrides?.executionConfig?.normalResponseTime ?? loaded.executionConfig?.normalResponseTime ?? DEFAULT_CONFIG_DATA.executionConfig?.normalResponseTime ?? "1000",
|
|
71
|
-
monitoredDomains: overrides?.executionConfig?.monitoredDomains ?? loaded.executionConfig?.monitoredDomains ?? DEFAULT_CONFIG_DATA.executionConfig?.monitoredDomains ?? [],
|
|
72
|
-
environmentVariables: overrides?.executionConfig?.environmentVariables ?? loaded.executionConfig?.environmentVariables ?? DEFAULT_CONFIG_DATA.executionConfig?.environmentVariables ?? [],
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
static saveConfig(cwd: string, config: ConfigData, filename = "testspectra.config.json"): string {
|
|
78
|
-
const target = path.join(cwd, filename);
|
|
79
|
-
fs.writeFileSync(target, JSON.stringify(config, null, 2), "utf-8");
|
|
80
|
-
return target;
|
|
81
|
-
}
|
|
82
|
-
}
|