nativeproof 0.1.1
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/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/dist/adb.d.ts +28 -0
- package/dist/adb.js +97 -0
- package/dist/app.d.ts +49 -0
- package/dist/app.js +34 -0
- package/dist/cli.d.ts +32 -0
- package/dist/cli.js +225 -0
- package/dist/config.d.ts +70 -0
- package/dist/config.js +64 -0
- package/dist/driver.d.ts +20 -0
- package/dist/driver.js +13 -0
- package/dist/evidence.d.ts +5 -0
- package/dist/evidence.js +40 -0
- package/dist/expect.d.ts +26 -0
- package/dist/expect.js +65 -0
- package/dist/fixtures.d.ts +62 -0
- package/dist/fixtures.js +53 -0
- package/dist/gestures.d.ts +11 -0
- package/dist/gestures.js +45 -0
- package/dist/harness.d.ts +32 -0
- package/dist/harness.js +29 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +30 -0
- package/dist/ios.d.ts +39 -0
- package/dist/ios.js +92 -0
- package/dist/locator.d.ts +78 -0
- package/dist/locator.js +116 -0
- package/dist/log.d.ts +17 -0
- package/dist/log.js +25 -0
- package/dist/mock-server.d.ts +17 -0
- package/dist/mock-server.js +122 -0
- package/dist/mock.d.ts +54 -0
- package/dist/mock.js +30 -0
- package/dist/page.d.ts +24 -0
- package/dist/page.js +17 -0
- package/dist/runner-config.d.ts +1 -0
- package/dist/runner-config.js +32 -0
- package/dist/runner.d.ts +19 -0
- package/dist/runner.js +29 -0
- package/dist/screen.d.ts +35 -0
- package/dist/screen.js +67 -0
- package/dist/source.d.ts +32 -0
- package/dist/source.js +60 -0
- package/dist/test.d.ts +13 -0
- package/dist/test.js +15 -0
- package/dist/text.d.ts +18 -0
- package/dist/text.js +129 -0
- package/dist/wait.d.ts +14 -0
- package/dist/wait.js +26 -0
- package/package.json +72 -0
package/dist/text.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { $, browser, driver, expect } from "@wdio/globals";
|
|
2
|
+
/**
|
|
3
|
+
* Cross-platform text/selector layer.
|
|
4
|
+
*
|
|
5
|
+
* Resolves the same human-readable control name to the right native selector on
|
|
6
|
+
* each platform (Android `UiSelector`, iOS predicate strings), preferring
|
|
7
|
+
* accessibility names with visible-text fallbacks. This is the single place a
|
|
8
|
+
* brittle selector becomes a stable automation id once the app teams add them.
|
|
9
|
+
* App-agnostic framework core.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_WAIT_MS = 15_000;
|
|
12
|
+
export function exactText(text) {
|
|
13
|
+
if (driver.isAndroid) {
|
|
14
|
+
return `android=new UiSelector().text(${JSON.stringify(text)})`;
|
|
15
|
+
}
|
|
16
|
+
const literal = iosLiteral(text);
|
|
17
|
+
return `-ios predicate string:name == ${literal} OR label == ${literal} OR value == ${literal}`;
|
|
18
|
+
}
|
|
19
|
+
export function textContaining(text) {
|
|
20
|
+
if (driver.isAndroid) {
|
|
21
|
+
return `android=new UiSelector().textContains(${JSON.stringify(text)})`;
|
|
22
|
+
}
|
|
23
|
+
const literal = iosLiteral(text);
|
|
24
|
+
return `-ios predicate string:name CONTAINS ${literal} OR label CONTAINS ${literal} OR value CONTAINS ${literal}`;
|
|
25
|
+
}
|
|
26
|
+
export function accessibleName(text) {
|
|
27
|
+
return `~${text}`;
|
|
28
|
+
}
|
|
29
|
+
/** A trimmed, non-empty environment credential, or undefined when not configured. */
|
|
30
|
+
export function configuredCredential(name) {
|
|
31
|
+
const value = process.env[name]?.trim();
|
|
32
|
+
return value && value.length > 0 ? value : undefined;
|
|
33
|
+
}
|
|
34
|
+
export function firstTextInput() {
|
|
35
|
+
if (driver.isAndroid) {
|
|
36
|
+
return 'android=new UiSelector().className("android.widget.EditText").instance(0)';
|
|
37
|
+
}
|
|
38
|
+
return "-ios class chain:**/XCUIElementTypeTextField[1]";
|
|
39
|
+
}
|
|
40
|
+
export function firstPasswordInput() {
|
|
41
|
+
if (driver.isAndroid) {
|
|
42
|
+
return 'android=new UiSelector().className("android.widget.EditText").instance(1)';
|
|
43
|
+
}
|
|
44
|
+
return "-ios class chain:**/XCUIElementTypeSecureTextField[1]";
|
|
45
|
+
}
|
|
46
|
+
export async function tapVisibleText(text, timeout = DEFAULT_WAIT_MS) {
|
|
47
|
+
const element = await visibleElementMatchingText(text, timeout);
|
|
48
|
+
await element.click();
|
|
49
|
+
}
|
|
50
|
+
export async function tapTextIfVisible(text, timeout = 1_000) {
|
|
51
|
+
try {
|
|
52
|
+
const element = await visibleElementMatchingText(text, timeout);
|
|
53
|
+
await element.click();
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function expectVisibleText(text, timeout = DEFAULT_WAIT_MS) {
|
|
61
|
+
const element = await visibleElementMatchingText(text, timeout);
|
|
62
|
+
await expect(element).toBeDisplayed();
|
|
63
|
+
}
|
|
64
|
+
export async function waitForAnyVisibleText(labels, timeout = DEFAULT_WAIT_MS) {
|
|
65
|
+
const deadline = Date.now() + timeout;
|
|
66
|
+
let lastError;
|
|
67
|
+
while (Date.now() < deadline) {
|
|
68
|
+
for (const label of labels) {
|
|
69
|
+
try {
|
|
70
|
+
const element = await visibleElementMatchingText(label, 500);
|
|
71
|
+
return { label, element };
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
lastError = error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
await browser.pause(500);
|
|
78
|
+
}
|
|
79
|
+
throw new Error(`None of these labels became visible: ${labels.join(", ")}${lastError instanceof Error ? `; last error: ${lastError.message}` : ""}`);
|
|
80
|
+
}
|
|
81
|
+
export async function waitForPageSourceToMention(phrases, timeout = DEFAULT_WAIT_MS) {
|
|
82
|
+
await browser.waitUntil(async () => {
|
|
83
|
+
const source = await driver.getPageSource();
|
|
84
|
+
return phrases.some((phrase) => source.toLowerCase().includes(phrase.toLowerCase()));
|
|
85
|
+
}, { timeout, timeoutMsg: `Page source did not mention any of: ${phrases.join(", ")}` });
|
|
86
|
+
}
|
|
87
|
+
export async function expectPageSourceToMention(phrases) {
|
|
88
|
+
const source = await driver.getPageSource();
|
|
89
|
+
const matched = phrases.some((phrase) => source.toLowerCase().includes(phrase.toLowerCase()));
|
|
90
|
+
await expect(matched).toBe(true);
|
|
91
|
+
}
|
|
92
|
+
export async function typeInto(selector, value) {
|
|
93
|
+
const input = await $(selector);
|
|
94
|
+
await input.waitForDisplayed({ timeout: DEFAULT_WAIT_MS });
|
|
95
|
+
await input.click();
|
|
96
|
+
await input.setValue(value);
|
|
97
|
+
}
|
|
98
|
+
async function visibleElementMatchingText(text, timeout) {
|
|
99
|
+
const deadline = Date.now() + timeout;
|
|
100
|
+
let lastError;
|
|
101
|
+
while (Date.now() < deadline) {
|
|
102
|
+
for (const selector of [accessibleName(text), exactText(text)]) {
|
|
103
|
+
try {
|
|
104
|
+
const element = await $(selector);
|
|
105
|
+
if ((await element.isDisplayed()) && (await elementCenterIsInsideViewport(element))) {
|
|
106
|
+
return element;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
lastError = error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
await browser.pause(300);
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`No visible element for "${text}"${lastError instanceof Error ? `; last error: ${lastError.message}` : ""}`);
|
|
116
|
+
}
|
|
117
|
+
async function elementCenterIsInsideViewport(element) {
|
|
118
|
+
const [location, size, viewport] = await Promise.all([
|
|
119
|
+
element.getLocation(),
|
|
120
|
+
element.getSize(),
|
|
121
|
+
driver.getWindowRect(),
|
|
122
|
+
]);
|
|
123
|
+
const centerX = location.x + size.width / 2;
|
|
124
|
+
const centerY = location.y + size.height / 2;
|
|
125
|
+
return centerX >= 0 && centerX <= viewport.width && centerY >= 0 && centerY <= viewport.height;
|
|
126
|
+
}
|
|
127
|
+
function iosLiteral(text) {
|
|
128
|
+
return `"${text.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
129
|
+
}
|
package/dist/wait.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { $ } from "@wdio/globals";
|
|
2
|
+
/**
|
|
3
|
+
* Selector waiting helpers built on the WebdriverIO runner globals.
|
|
4
|
+
*
|
|
5
|
+
* `waitForAnyDisplayed` resolves the first of several candidate selectors to
|
|
6
|
+
* appear — essential on screens that render one of multiple possible states
|
|
7
|
+
* (e.g. an onboarding "Later" sheet vs. the role-selection home).
|
|
8
|
+
*/
|
|
9
|
+
export type DisplayedMatch = {
|
|
10
|
+
selector: string;
|
|
11
|
+
element: ReturnType<typeof $>;
|
|
12
|
+
};
|
|
13
|
+
export declare function waitAndClick(selector: string, timeout?: number): Promise<void>;
|
|
14
|
+
export declare function waitForAnyDisplayed(selectors: string[], timeout?: number): Promise<DisplayedMatch>;
|
package/dist/wait.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { $, browser } from "@wdio/globals";
|
|
2
|
+
export async function waitAndClick(selector, timeout = 10000) {
|
|
3
|
+
const element = await $(selector);
|
|
4
|
+
await element.waitForDisplayed({ timeout });
|
|
5
|
+
await element.click();
|
|
6
|
+
}
|
|
7
|
+
export async function waitForAnyDisplayed(selectors, timeout = 30000) {
|
|
8
|
+
let matched;
|
|
9
|
+
await browser.waitUntil(async () => {
|
|
10
|
+
for (const selector of selectors) {
|
|
11
|
+
const element = $(selector);
|
|
12
|
+
const displayed = await element.isDisplayed().catch(() => false);
|
|
13
|
+
if (displayed) {
|
|
14
|
+
matched = { selector, element };
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}, {
|
|
20
|
+
timeout,
|
|
21
|
+
interval: 500,
|
|
22
|
+
timeoutMsg: `None of the selectors became displayed within ${timeout}ms: ${selectors.join(", ")}`,
|
|
23
|
+
});
|
|
24
|
+
// waitUntil only resolves once the predicate set `matched`.
|
|
25
|
+
return matched;
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nativeproof",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Native Mobile E2E test framework inspired by Playwright (fixtures, locators, expect, route-style mocking) on Appium/WebdriverIO.",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"appium",
|
|
9
|
+
"webdriverio",
|
|
10
|
+
"e2e",
|
|
11
|
+
"end-to-end",
|
|
12
|
+
"mobile",
|
|
13
|
+
"android",
|
|
14
|
+
"ios",
|
|
15
|
+
"testing",
|
|
16
|
+
"test-framework",
|
|
17
|
+
"playwright",
|
|
18
|
+
"xcuitest",
|
|
19
|
+
"uiautomator2"
|
|
20
|
+
],
|
|
21
|
+
"author": "Ben Sheridan-Edwards",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/BenSheridanEdwards/NativeProof.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/BenSheridanEdwards/NativeProof#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/BenSheridanEdwards/NativeProof/issues"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"nativeproof": "dist/cli.js"
|
|
32
|
+
},
|
|
33
|
+
"main": "dist/index.js",
|
|
34
|
+
"types": "dist/index.d.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"default": "./dist/index.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"CHANGELOG.md"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc -p tsconfig.build.json",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"lint": "biome lint .",
|
|
49
|
+
"format": "biome format --write .",
|
|
50
|
+
"check": "biome check . && tsc --noEmit",
|
|
51
|
+
"test": "node --import tsx --test 'test/**/*.test.ts'",
|
|
52
|
+
"prepare": "npm run build",
|
|
53
|
+
"prepublishOnly": "npm run check && npm test"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@wdio/globals": "^9.17.0",
|
|
57
|
+
"webdriverio": "^9.21.1",
|
|
58
|
+
"ws": "^8.18.3"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@biomejs/biome": "^2.4.16",
|
|
62
|
+
"@types/node": "^24.10.1",
|
|
63
|
+
"@types/ws": "^8.18.1",
|
|
64
|
+
"@wdio/mocha-framework": "^9.21.0",
|
|
65
|
+
"expect-webdriverio": "^5.5.0",
|
|
66
|
+
"tsx": "^4.20.6",
|
|
67
|
+
"typescript": "^5.9.3"
|
|
68
|
+
},
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=20"
|
|
71
|
+
}
|
|
72
|
+
}
|