@testspectra/cli 1.0.10 → 1.0.12
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.d.ts +4 -2
- package/dist/commands/init.js +38 -5
- package/dist/commands/run.js +24 -17
- package/dist/index.js +1 -0
- package/package.json +2 -2
- package/templates/default/tsconfig.android.json +6 -6
- package/templates/default/tsconfig.ios.json +6 -6
- package/templates/default/tsconfig.web.json +8 -8
- package/templates/nx/actions/applyDiscount/mobile.action.ts +6 -0
- package/templates/nx/actions/applyDiscount/web.action.ts +6 -0
- package/templates/nx/actions/verifyOtp/mobile.action.ts +6 -0
- package/templates/nx/actions/verifyOtp/web.action.ts +6 -0
- package/templates/nx/default/actions/applyDiscount/mobile.action.ts +6 -0
- package/templates/nx/default/actions/applyDiscount/web.action.ts +6 -0
- package/templates/nx/default/actions/verifyOtp/mobile.action.ts +6 -0
- package/templates/nx/default/actions/verifyOtp/web.action.ts +6 -0
- package/templates/nx/default/fixtures/couponCode.json +1 -0
- package/templates/nx/default/fixtures/searchQuery.json +1 -0
- package/templates/nx/default/fixtures/userData.json +1 -0
- package/templates/nx/default/global-hooks/before.web.hook.ts +4 -0
- package/templates/nx/default/package.json +18 -0
- package/templates/nx/default/page-objects/LoginPage/mobile.ts +32 -0
- package/templates/nx/default/page-objects/LoginPage/web.ts +32 -0
- package/templates/nx/default/page-objects/ProductPage/mobile.ts +39 -0
- package/templates/nx/default/page-objects/ProductPage/web.ts +38 -0
- package/templates/nx/default/page-objects/SettingsPage/common.ts +26 -0
- package/templates/nx/default/specs/Auth/TC-AUTH-01/android.test.ts +6 -0
- package/templates/nx/default/specs/Auth/TC-AUTH-01/ios.test.ts +6 -0
- package/templates/nx/default/specs/Auth/TC-AUTH-01/web.test.ts +13 -0
- package/templates/nx/default/specs/Auth/TC-AUTH-02/android.test.ts +5 -0
- package/templates/nx/default/specs/Auth/TC-AUTH-02/web.test.ts +5 -0
- package/templates/nx/default/specs/Auth/hooks/before.android.hook.ts +3 -0
- package/templates/nx/default/specs/Auth/hooks/before.ios.hook.ts +3 -0
- package/templates/nx/default/specs/Auth/hooks/before.web.hook.ts +3 -0
- package/templates/nx/default/specs/Product/TC-PROD-01/mobile.test.ts +8 -0
- package/templates/nx/default/specs/Product/TC-PROD-01/web.test.ts +8 -0
- package/templates/nx/default/specs/Product/TC-PROD-02/mobile.test.ts +6 -0
- package/templates/nx/default/specs/Product/TC-PROD-02/web.test.ts +6 -0
- package/templates/nx/default/specs/Product/hooks/before.web.hook.ts +3 -0
- package/templates/nx/default/specs/Settings/TC-SET-01/common.test.ts +8 -0
- package/templates/nx/default/spectra.config.ts +61 -0
- package/templates/nx/default/steps/loginUser/mobile.step.ts +4 -0
- package/templates/nx/default/steps/loginUser/web.step.ts +4 -0
- package/templates/nx/default/steps/searchProduct/mobile.step.ts +5 -0
- package/templates/nx/default/steps/searchProduct/web.step.ts +5 -0
- package/templates/nx/default/tsconfig.android.json +39 -0
- package/templates/nx/default/tsconfig.ios.json +39 -0
- package/templates/nx/default/tsconfig.json +20 -0
- package/templates/nx/default/tsconfig.web.json +45 -0
- package/templates/nx/fixtures/couponCode.json +1 -0
- package/templates/nx/fixtures/searchQuery.json +1 -0
- package/templates/nx/fixtures/userData.json +1 -0
- package/templates/nx/global-hooks/before.web.hook.ts +4 -0
- package/templates/nx/package.json +18 -0
- package/templates/nx/page-objects/LoginPage/mobile.ts +32 -0
- package/templates/nx/page-objects/LoginPage/web.ts +32 -0
- package/templates/nx/page-objects/ProductPage/mobile.ts +39 -0
- package/templates/nx/page-objects/ProductPage/web.ts +38 -0
- package/templates/nx/page-objects/SettingsPage/common.ts +26 -0
- package/templates/nx/project.json +34 -0
- package/templates/nx/specs/Auth/TC-AUTH-01/android.test.ts +6 -0
- package/templates/nx/specs/Auth/TC-AUTH-01/ios.test.ts +6 -0
- package/templates/nx/specs/Auth/TC-AUTH-01/web.test.ts +13 -0
- package/templates/nx/specs/Auth/TC-AUTH-02/android.test.ts +5 -0
- package/templates/nx/specs/Auth/TC-AUTH-02/web.test.ts +5 -0
- package/templates/nx/specs/Auth/hooks/before.android.hook.ts +3 -0
- package/templates/nx/specs/Auth/hooks/before.ios.hook.ts +3 -0
- package/templates/nx/specs/Auth/hooks/before.web.hook.ts +3 -0
- package/templates/nx/specs/Product/TC-PROD-01/mobile.test.ts +8 -0
- package/templates/nx/specs/Product/TC-PROD-01/web.test.ts +8 -0
- package/templates/nx/specs/Product/TC-PROD-02/mobile.test.ts +6 -0
- package/templates/nx/specs/Product/TC-PROD-02/web.test.ts +6 -0
- package/templates/nx/specs/Product/hooks/before.web.hook.ts +3 -0
- package/templates/nx/specs/Settings/TC-SET-01/common.test.ts +8 -0
- package/templates/nx/spectra.config.ts +61 -0
- package/templates/nx/steps/loginUser/mobile.step.ts +4 -0
- package/templates/nx/steps/loginUser/web.step.ts +4 -0
- package/templates/nx/steps/searchProduct/mobile.step.ts +5 -0
- package/templates/nx/steps/searchProduct/web.step.ts +5 -0
- package/templates/nx/tsconfig.android.json +39 -0
- package/templates/nx/tsconfig.ios.json +39 -0
- package/templates/nx/tsconfig.json +20 -0
- package/templates/nx/tsconfig.web.json +45 -0
- /package/templates/default/{suites → specs}/Auth/TC-AUTH-01/android.test.ts +0 -0
- /package/templates/default/{suites → specs}/Auth/TC-AUTH-01/ios.test.ts +0 -0
- /package/templates/default/{suites → specs}/Auth/TC-AUTH-01/web.test.ts +0 -0
- /package/templates/default/{suites → specs}/Auth/TC-AUTH-02/android.test.ts +0 -0
- /package/templates/default/{suites → specs}/Auth/TC-AUTH-02/web.test.ts +0 -0
- /package/templates/default/{suites → specs}/Auth/hooks/before.android.hook.ts +0 -0
- /package/templates/default/{suites → specs}/Auth/hooks/before.ios.hook.ts +0 -0
- /package/templates/default/{suites → specs}/Auth/hooks/before.web.hook.ts +0 -0
- /package/templates/default/{suites → specs}/Product/TC-PROD-01/mobile.test.ts +0 -0
- /package/templates/default/{suites → specs}/Product/TC-PROD-01/web.test.ts +0 -0
- /package/templates/default/{suites → specs}/Product/TC-PROD-02/mobile.test.ts +0 -0
- /package/templates/default/{suites → specs}/Product/TC-PROD-02/web.test.ts +0 -0
- /package/templates/default/{suites → specs}/Product/hooks/before.web.hook.ts +0 -0
- /package/templates/default/{suites → specs}/Settings/TC-SET-01/common.test.ts +0 -0
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -3,23 +3,51 @@ import path from "path";
|
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
import { ConfigLoader } from "../config/loader.js";
|
|
5
5
|
import { TypeGenerator } from "../types/generator.js";
|
|
6
|
-
|
|
6
|
+
import inquirer from "inquirer";
|
|
7
|
+
export async function initCommand(options = {}) {
|
|
7
8
|
const cwd = process.cwd();
|
|
8
9
|
const existingConfig = ConfigLoader.findConfigFile(cwd);
|
|
9
10
|
if (existingConfig && !options.force) {
|
|
10
11
|
console.log(`\x1b[33m[TestSpectra]\x1b[0m Config already exists at ${path.basename(existingConfig)}. Use --force to overwrite.`);
|
|
11
12
|
return;
|
|
12
13
|
}
|
|
14
|
+
// Interactive Prompt if template is not specified
|
|
15
|
+
let selectedTemplate = options.template;
|
|
16
|
+
if (!selectedTemplate) {
|
|
17
|
+
// Detect if we are inside an Nx workspace
|
|
18
|
+
let isNx = false;
|
|
19
|
+
let checkNx = cwd;
|
|
20
|
+
while (checkNx !== path.dirname(checkNx)) {
|
|
21
|
+
if (fs.existsSync(path.join(checkNx, "nx.json"))) {
|
|
22
|
+
isNx = true;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
checkNx = path.dirname(checkNx);
|
|
26
|
+
}
|
|
27
|
+
const answers = await inquirer.prompt([
|
|
28
|
+
{
|
|
29
|
+
type: "list",
|
|
30
|
+
name: "template",
|
|
31
|
+
message: "Select TestSpectra project template:",
|
|
32
|
+
choices: [
|
|
33
|
+
{ name: "Standard (Standalone or standard monorepo)", value: "default" },
|
|
34
|
+
{ name: "Nx Monorepo (Includes project.json with Nx targets & affected support)", value: "nx" },
|
|
35
|
+
],
|
|
36
|
+
default: isNx ? "nx" : "default",
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
selectedTemplate = answers.template;
|
|
40
|
+
}
|
|
41
|
+
const templateName = selectedTemplate || "default";
|
|
13
42
|
// 1. Resolve template directory
|
|
14
43
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
44
|
const __dirname = path.dirname(__filename);
|
|
16
|
-
|
|
17
|
-
let templateDir = path.resolve(__dirname, "../templates/default");
|
|
45
|
+
let templateDir = path.resolve(__dirname, `../templates/${templateName}`);
|
|
18
46
|
if (!fs.existsSync(templateDir)) {
|
|
19
|
-
templateDir = path.resolve(__dirname,
|
|
47
|
+
templateDir = path.resolve(__dirname, `../../templates/${templateName}`);
|
|
20
48
|
}
|
|
21
49
|
if (!fs.existsSync(templateDir)) {
|
|
22
|
-
templateDir = path.resolve(__dirname,
|
|
50
|
+
templateDir = path.resolve(__dirname, `../../../templates/${templateName}`);
|
|
23
51
|
}
|
|
24
52
|
if (!fs.existsSync(templateDir)) {
|
|
25
53
|
throw new Error(`[TestSpectra] Scaffold template directory not found at: ${templateDir}`);
|
|
@@ -86,6 +114,11 @@ export async function initCommand(options) {
|
|
|
86
114
|
}
|
|
87
115
|
catch { }
|
|
88
116
|
}
|
|
117
|
+
else if (src.endsWith("project.json")) {
|
|
118
|
+
content = content
|
|
119
|
+
.replace(/\{\{PROJECT_NAME\}\}/g, projectName)
|
|
120
|
+
.replace(/\{\{PROJECT_DIR\}\}/g, path.relative(cur, cwd) || ".");
|
|
121
|
+
}
|
|
89
122
|
fs.writeFileSync(dest, content, "utf-8");
|
|
90
123
|
}
|
|
91
124
|
}
|
package/dist/commands/run.js
CHANGED
|
@@ -31,7 +31,30 @@ export async function runCommand(specPath, options = {}) {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
else {
|
|
34
|
-
// Scan
|
|
34
|
+
// 1. Scan specs/ directory (Option A: specs/<Suite>/<CaseId>/web.test.ts)
|
|
35
|
+
const specsDir = path.join(cwd, "specs");
|
|
36
|
+
if (fs.existsSync(specsDir)) {
|
|
37
|
+
const suiteEntries = fs.readdirSync(specsDir, { withFileTypes: true });
|
|
38
|
+
for (const sEntry of suiteEntries) {
|
|
39
|
+
if (sEntry.name.startsWith(".") || !sEntry.isDirectory())
|
|
40
|
+
continue;
|
|
41
|
+
const currentSuite = sEntry.name;
|
|
42
|
+
const suitePath = path.join(specsDir, currentSuite);
|
|
43
|
+
const caseEntries = fs.readdirSync(suitePath, { withFileTypes: true });
|
|
44
|
+
for (const cEntry of caseEntries) {
|
|
45
|
+
if (cEntry.name.startsWith(".") || cEntry.name === "hooks")
|
|
46
|
+
continue;
|
|
47
|
+
if (cEntry.isDirectory()) {
|
|
48
|
+
testCases.push({ id: cEntry.name, title: `${currentSuite} / ${cEntry.name}` });
|
|
49
|
+
}
|
|
50
|
+
else if (cEntry.isFile() && (cEntry.name.endsWith(".test.ts") || cEntry.name.endsWith(".spec.ts"))) {
|
|
51
|
+
const id = cEntry.name.replace(/\.(web|android|ios|common|all|mobile|test|spec)\.ts$/g, "");
|
|
52
|
+
testCases.push({ id, title: `${currentSuite} / ${id}` });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// 2. Fallback: Scan suites/ directory
|
|
35
58
|
const suitesDir = path.join(cwd, "suites");
|
|
36
59
|
if (fs.existsSync(suitesDir)) {
|
|
37
60
|
const suiteEntries = fs.readdirSync(suitesDir, { withFileTypes: true });
|
|
@@ -48,22 +71,6 @@ export async function runCommand(specPath, options = {}) {
|
|
|
48
71
|
}
|
|
49
72
|
}
|
|
50
73
|
}
|
|
51
|
-
// Fallback: Scan legacy specs/ directory
|
|
52
|
-
const specsDir = path.join(cwd, "specs");
|
|
53
|
-
if (fs.existsSync(specsDir)) {
|
|
54
|
-
const entries = fs.readdirSync(specsDir, { withFileTypes: true });
|
|
55
|
-
for (const entry of entries) {
|
|
56
|
-
if (entry.name.startsWith("."))
|
|
57
|
-
continue;
|
|
58
|
-
if (entry.isDirectory()) {
|
|
59
|
-
testCases.push({ id: entry.name, title: entry.name });
|
|
60
|
-
}
|
|
61
|
-
else if (entry.isFile() && (entry.name.endsWith(".test.ts") || entry.name.endsWith(".spec.ts"))) {
|
|
62
|
-
const id = entry.name.replace(/\.(web|android|ios|common|all|mobile|test|spec)\.ts$/g, "").replace(/\.test\.ts$/, "");
|
|
63
|
-
testCases.push({ id, title: id });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
74
|
}
|
|
68
75
|
if (testCases.length === 0) {
|
|
69
76
|
testCases.push({
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export function createCliProgram() {
|
|
|
17
17
|
program
|
|
18
18
|
.command("init")
|
|
19
19
|
.description("Initialize testspectra.config.json and project directories")
|
|
20
|
+
.option("-t, --template <template>", "Project template: default, nx")
|
|
20
21
|
.option("-f, --force", "Overwrite existing configuration if present")
|
|
21
22
|
.action(initCommand);
|
|
22
23
|
program
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testspectra/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "TestSpectra Zero-Config Cross-Platform Test Runner CLI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@testspectra/matchers": "
|
|
23
|
+
"@testspectra/matchers": "^1.0.0",
|
|
24
24
|
"chalk": "^5.3.0",
|
|
25
25
|
"chokidar": "^3.6.0",
|
|
26
26
|
"commander": "^12.1.0",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"steps/**/*.ts",
|
|
17
17
|
"actions/**/*.ts",
|
|
18
18
|
"global-hooks/**/*.ts",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"specs/**/hooks/**/*.ts",
|
|
20
|
+
"specs/**/*.ts",
|
|
21
21
|
"spectra.config.ts"
|
|
22
22
|
],
|
|
23
23
|
"exclude": [
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"actions/**/ios.action.ts",
|
|
32
32
|
"global-hooks/**/web.hook.ts",
|
|
33
33
|
"global-hooks/**/ios.hook.ts",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
34
|
+
"specs/**/hooks/**/web.hook.ts",
|
|
35
|
+
"specs/**/hooks/**/ios.hook.ts",
|
|
36
|
+
"specs/**/web.test.ts",
|
|
37
|
+
"specs/**/ios.test.ts"
|
|
38
38
|
]
|
|
39
39
|
}
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"steps/**/*.ts",
|
|
17
17
|
"actions/**/*.ts",
|
|
18
18
|
"global-hooks/**/*.ts",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"specs/**/hooks/**/*.ts",
|
|
20
|
+
"specs/**/*.ts",
|
|
21
21
|
"spectra.config.ts"
|
|
22
22
|
],
|
|
23
23
|
"exclude": [
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"actions/**/android.action.ts",
|
|
32
32
|
"global-hooks/**/web.hook.ts",
|
|
33
33
|
"global-hooks/**/android.hook.ts",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
34
|
+
"specs/**/hooks/**/web.hook.ts",
|
|
35
|
+
"specs/**/hooks/**/android.hook.ts",
|
|
36
|
+
"specs/**/web.test.ts",
|
|
37
|
+
"specs/**/android.test.ts"
|
|
38
38
|
]
|
|
39
39
|
}
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"steps/**/*.ts",
|
|
17
17
|
"actions/**/*.ts",
|
|
18
18
|
"global-hooks/**/*.ts",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"specs/**/hooks/**/*.ts",
|
|
20
|
+
"specs/**/*.ts",
|
|
21
21
|
"spectra.config.ts"
|
|
22
22
|
],
|
|
23
23
|
"exclude": [
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"global-hooks/**/android.hook.ts",
|
|
36
36
|
"global-hooks/**/ios.hook.ts",
|
|
37
37
|
"global-hooks/**/mobile.hook.ts",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
38
|
+
"specs/**/hooks/**/android.hook.ts",
|
|
39
|
+
"specs/**/hooks/**/ios.hook.ts",
|
|
40
|
+
"specs/**/hooks/**/mobile.hook.ts",
|
|
41
|
+
"specs/**/android.test.ts",
|
|
42
|
+
"specs/**/ios.test.ts",
|
|
43
|
+
"specs/**/mobile.test.ts"
|
|
44
44
|
]
|
|
45
45
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"DISCOUNT50"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"Laptop Gaming"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"username": "tomsmith", "role": "admin"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@testspectra/template-default",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "spectra run",
|
|
8
|
+
"type-check": "tsc -b"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@testspectra/cli": "workspace:*",
|
|
12
|
+
"@types/node": "^20.14.0",
|
|
13
|
+
"@wdio/globals": "^9.2.8",
|
|
14
|
+
"@wdio/mocha-framework": "^9.2.8",
|
|
15
|
+
"webdriverio": "^9.2.8",
|
|
16
|
+
"typescript": "^5.4.5"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export default class LoginPage {
|
|
2
|
+
static get usernameInput(): ChainablePromiseElement {
|
|
3
|
+
return $('~username_input');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
static get passwordInput(): ChainablePromiseElement {
|
|
7
|
+
return $('~password_input');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static get rememberMeCheckbox(): ChainablePromiseElement {
|
|
11
|
+
return $('~remember_me_checkbox');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static get submitButton(): ChainablePromiseElement {
|
|
15
|
+
return $('~login_button');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static get welcomeText(): ChainablePromiseElement {
|
|
19
|
+
return $('~welcome_text');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static async open() {
|
|
23
|
+
// Mobile app launch handled by Appium capabilities
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static async login(username: string, pass: string) {
|
|
27
|
+
await Spectra.waitForElement(this.usernameInput);
|
|
28
|
+
await Spectra.type(this.usernameInput, username, { clearFirst: true });
|
|
29
|
+
await Spectra.type(this.passwordInput, pass, { clearFirst: true });
|
|
30
|
+
await Spectra.click(this.submitButton);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export default class LoginPage {
|
|
2
|
+
static get usernameInput(): ChainablePromiseElement {
|
|
3
|
+
return $('#username');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
static get passwordInput(): ChainablePromiseElement {
|
|
7
|
+
return $('#password');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static get rememberMeCheckbox(): ChainablePromiseElement {
|
|
11
|
+
return $('#remember-me');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static get submitButton(): ChainablePromiseElement {
|
|
15
|
+
return $('button[type="submit"]');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static get flashAlert(): ChainablePromiseElement {
|
|
19
|
+
return $('#flash');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static async open() {
|
|
23
|
+
await Spectra.navigate('/login');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static async login(username: string, pass: string) {
|
|
27
|
+
await Spectra.waitForElement(this.usernameInput);
|
|
28
|
+
await Spectra.type(this.usernameInput, username, { clearFirst: true });
|
|
29
|
+
await Spectra.type(this.passwordInput, pass, { clearFirst: true });
|
|
30
|
+
await Spectra.click(this.submitButton);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export default class ProductPage {
|
|
2
|
+
static get searchInput(): ChainablePromiseElement {
|
|
3
|
+
return $('~search_input');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
static get categorySelect(): ChainablePromiseElement {
|
|
7
|
+
return $('~category_picker');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static get productList(): ChainablePromiseElement {
|
|
11
|
+
return $('~product_list');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static get firstProductCard(): ChainablePromiseElement {
|
|
15
|
+
return $('~product_item_0');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static get addToCartButton(): ChainablePromiseElement {
|
|
19
|
+
return $('~btn_add_to_cart');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static get cartBadge(): ChainablePromiseElement {
|
|
23
|
+
return $('~cart_badge');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static async open() {
|
|
27
|
+
// Mobile in-app navigation to products tab
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static async filterByCategory(category: string) {
|
|
31
|
+
await Spectra.click(this.categorySelect);
|
|
32
|
+
await Spectra.click(`~option_${category}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static async addFirstProductToCart() {
|
|
36
|
+
await Spectra.swipe({ direction: "down", distance: 400 });
|
|
37
|
+
await Spectra.click(this.addToCartButton);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export default class ProductPage {
|
|
2
|
+
static get searchInput(): ChainablePromiseElement {
|
|
3
|
+
return $('#search-input');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
static get categorySelect(): ChainablePromiseElement {
|
|
7
|
+
return $('#category-select');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static get productList(): ChainablePromiseElement {
|
|
11
|
+
return $('#product-grid');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static get firstProductCard(): ChainablePromiseElement {
|
|
15
|
+
return $('.product-card');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static get addToCartButton(): ChainablePromiseElement {
|
|
19
|
+
return $('#add-to-cart');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static get cartBadge(): ChainablePromiseElement {
|
|
23
|
+
return $('#cart-count');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static async open() {
|
|
27
|
+
await Spectra.navigate('/products');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static async filterByCategory(category: string) {
|
|
31
|
+
await Spectra.select(this.categorySelect, category);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static async addFirstProductToCart() {
|
|
35
|
+
await Spectra.scroll({ selector: this.firstProductCard });
|
|
36
|
+
await Spectra.click(this.addToCartButton);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export default class SettingsPage {
|
|
2
|
+
static get darkModeToggle(): ChainablePromiseElement {
|
|
3
|
+
return $('[data-testid="dark-mode-toggle"]');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
static get languageDropdown(): ChainablePromiseElement {
|
|
7
|
+
return $('[data-testid="language-select"]');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static get saveButton(): ChainablePromiseElement {
|
|
11
|
+
return $('[data-testid="save-settings"]');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static get statusMessage(): ChainablePromiseElement {
|
|
15
|
+
return $('[data-testid="status-message"]');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static async open() {
|
|
19
|
+
await Spectra.navigate('/settings');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static async toggleDarkMode() {
|
|
23
|
+
await Spectra.click(this.darkModeToggle);
|
|
24
|
+
await Spectra.click(this.saveButton);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
it("should authenticate user and verify flash message across specific platforms", async () => {
|
|
2
|
+
await browser.intercept("/api/status", "GET", Fixture.userData);
|
|
3
|
+
|
|
4
|
+
// 1. Navigation via Page Object
|
|
5
|
+
await LoginPage.open();
|
|
6
|
+
|
|
7
|
+
// 2. High-level business flow via Shared Step & Page Object
|
|
8
|
+
await Step.loginUser("tomsmith", "SuperSecretPassword!");
|
|
9
|
+
|
|
10
|
+
// 3. Direct callable assertion on Page Object element
|
|
11
|
+
await LoginPage.flashAlert.shouldBeVisible();
|
|
12
|
+
await LoginPage.flashAlert.shouldContainText("You logged into a secure area!");
|
|
13
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
it("should filter products by category and add to cart on Mobile (Android & iOS)", async () => {
|
|
2
|
+
await ProductPage.open();
|
|
3
|
+
await ProductPage.filterByCategory("Electronics");
|
|
4
|
+
await ProductPage.addFirstProductToCart();
|
|
5
|
+
|
|
6
|
+
await ProductPage.cartBadge.shouldBeVisible();
|
|
7
|
+
await ProductPage.cartBadge.shouldHaveText("1");
|
|
8
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
it("should filter products by category and add to cart on Web", async () => {
|
|
2
|
+
await ProductPage.open();
|
|
3
|
+
await ProductPage.filterByCategory("Electronics");
|
|
4
|
+
await ProductPage.addFirstProductToCart();
|
|
5
|
+
|
|
6
|
+
await ProductPage.cartBadge.shouldBeVisible();
|
|
7
|
+
await ProductPage.cartBadge.shouldHaveText("1");
|
|
8
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
it("should toggle dark mode setting across all platforms (Common)", async () => {
|
|
2
|
+
await SettingsPage.open();
|
|
3
|
+
await SettingsPage.darkModeToggle.shouldBeVisible();
|
|
4
|
+
await SettingsPage.toggleDarkMode();
|
|
5
|
+
|
|
6
|
+
await SettingsPage.statusMessage.shouldBeVisible();
|
|
7
|
+
await SettingsPage.statusMessage.shouldContainText("Settings saved");
|
|
8
|
+
});
|