@taqwright/taqwright 0.0.24

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.
Files changed (132) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +108 -0
  3. package/dist/auto-appium.d.ts +12 -0
  4. package/dist/auto-appium.js +77 -0
  5. package/dist/bin/branding.d.ts +6 -0
  6. package/dist/bin/branding.js +22 -0
  7. package/dist/bin/index.d.ts +2 -0
  8. package/dist/bin/index.js +321 -0
  9. package/dist/bin/init.d.ts +26 -0
  10. package/dist/bin/init.js +902 -0
  11. package/dist/bin/inspect.d.ts +9 -0
  12. package/dist/bin/inspect.js +91 -0
  13. package/dist/bin/report-branding.d.ts +2 -0
  14. package/dist/bin/report-branding.js +42 -0
  15. package/dist/branding-assets.d.ts +1 -0
  16. package/dist/branding-assets.js +1 -0
  17. package/dist/capabilities-helpers.d.ts +7 -0
  18. package/dist/capabilities-helpers.js +14 -0
  19. package/dist/capabilities.d.ts +6 -0
  20. package/dist/capabilities.js +86 -0
  21. package/dist/config.d.ts +17 -0
  22. package/dist/config.js +235 -0
  23. package/dist/discovery-setup.d.ts +1 -0
  24. package/dist/discovery-setup.js +61 -0
  25. package/dist/discovery.d.ts +17 -0
  26. package/dist/discovery.js +55 -0
  27. package/dist/docs/configuration.html +376 -0
  28. package/dist/docs/custom-reporters.html +265 -0
  29. package/dist/docs/docker.html +339 -0
  30. package/dist/docs/docs.js +173 -0
  31. package/dist/docs/generating-tests.html +161 -0
  32. package/dist/docs/images/taqwright-html-report.png +0 -0
  33. package/dist/docs/index.html +13 -0
  34. package/dist/docs/installation.html +686 -0
  35. package/dist/docs/parallel.html +271 -0
  36. package/dist/docs/running-tests.html +385 -0
  37. package/dist/docs/styles.css +460 -0
  38. package/dist/docs/writing-tests.html +565 -0
  39. package/dist/doctor.d.ts +33 -0
  40. package/dist/doctor.js +508 -0
  41. package/dist/expect.d.ts +38 -0
  42. package/dist/expect.js +96 -0
  43. package/dist/fixture/artifact-mode.d.ts +2 -0
  44. package/dist/fixture/artifact-mode.js +7 -0
  45. package/dist/fixture/index.d.ts +15 -0
  46. package/dist/fixture/index.js +324 -0
  47. package/dist/images/taqwright-html-report.png +0 -0
  48. package/dist/images/taqwright_favicon.png +0 -0
  49. package/dist/images/taqwright_logo.png +0 -0
  50. package/dist/index.d.ts +9 -0
  51. package/dist/index.js +7 -0
  52. package/dist/inspector/codegen-appium.d.ts +3 -0
  53. package/dist/inspector/codegen-appium.js +228 -0
  54. package/dist/inspector/devices.d.ts +41 -0
  55. package/dist/inspector/devices.js +422 -0
  56. package/dist/inspector/locator-suggester.d.ts +23 -0
  57. package/dist/inspector/locator-suggester.js +539 -0
  58. package/dist/inspector/recorder.d.ts +128 -0
  59. package/dist/inspector/recorder.js +162 -0
  60. package/dist/inspector/server.d.ts +39 -0
  61. package/dist/inspector/server.js +1210 -0
  62. package/dist/inspector/session.d.ts +84 -0
  63. package/dist/inspector/session.js +262 -0
  64. package/dist/inspector/ui.d.ts +1 -0
  65. package/dist/inspector/ui.js +5508 -0
  66. package/dist/keys.d.ts +3 -0
  67. package/dist/keys.js +28 -0
  68. package/dist/locator/index.d.ts +206 -0
  69. package/dist/locator/index.js +1506 -0
  70. package/dist/logger.d.ts +5 -0
  71. package/dist/logger.js +5 -0
  72. package/dist/mobile/index.d.ts +130 -0
  73. package/dist/mobile/index.js +762 -0
  74. package/dist/network/android.d.ts +5 -0
  75. package/dist/network/android.js +87 -0
  76. package/dist/network/ca.d.ts +10 -0
  77. package/dist/network/ca.js +136 -0
  78. package/dist/network/har.d.ts +90 -0
  79. package/dist/network/har.js +101 -0
  80. package/dist/network/host-proxy.d.ts +16 -0
  81. package/dist/network/host-proxy.js +134 -0
  82. package/dist/network/index.d.ts +26 -0
  83. package/dist/network/index.js +105 -0
  84. package/dist/network/ios-sim.d.ts +3 -0
  85. package/dist/network/ios-sim.js +29 -0
  86. package/dist/network/proxy.d.ts +13 -0
  87. package/dist/network/proxy.js +310 -0
  88. package/dist/providers/appium.d.ts +23 -0
  89. package/dist/providers/appium.js +288 -0
  90. package/dist/providers/browserstack/index.d.ts +5 -0
  91. package/dist/providers/browserstack/index.js +77 -0
  92. package/dist/providers/browserstack/utils.d.ts +1 -0
  93. package/dist/providers/browserstack/utils.js +6 -0
  94. package/dist/providers/cloud.d.ts +53 -0
  95. package/dist/providers/cloud.js +117 -0
  96. package/dist/providers/emulator/index.d.ts +8 -0
  97. package/dist/providers/emulator/index.js +47 -0
  98. package/dist/providers/index.d.ts +10 -0
  99. package/dist/providers/index.js +33 -0
  100. package/dist/providers/lambdatest/index.d.ts +28 -0
  101. package/dist/providers/lambdatest/index.js +99 -0
  102. package/dist/providers/lambdatest/utils.d.ts +1 -0
  103. package/dist/providers/lambdatest/utils.js +6 -0
  104. package/dist/providers/local/index.d.ts +9 -0
  105. package/dist/providers/local/index.js +53 -0
  106. package/dist/providers/local-session.d.ts +16 -0
  107. package/dist/providers/local-session.js +55 -0
  108. package/dist/setup/archive.d.ts +2 -0
  109. package/dist/setup/archive.js +43 -0
  110. package/dist/setup/avd.d.ts +12 -0
  111. package/dist/setup/avd.js +103 -0
  112. package/dist/setup/index.d.ts +6 -0
  113. package/dist/setup/index.js +55 -0
  114. package/dist/setup/install-android.d.ts +2 -0
  115. package/dist/setup/install-android.js +70 -0
  116. package/dist/setup/install-appium.d.ts +1 -0
  117. package/dist/setup/install-appium.js +64 -0
  118. package/dist/setup/install-jdk.d.ts +1 -0
  119. package/dist/setup/install-jdk.js +58 -0
  120. package/dist/setup/paths.d.ts +16 -0
  121. package/dist/setup/paths.js +88 -0
  122. package/dist/setup/spawn-tool.d.ts +3 -0
  123. package/dist/setup/spawn-tool.js +11 -0
  124. package/dist/tracer/index.d.ts +34 -0
  125. package/dist/tracer/index.js +687 -0
  126. package/dist/tracer/proxy.d.ts +3 -0
  127. package/dist/tracer/proxy.js +60 -0
  128. package/dist/types/index.d.ts +189 -0
  129. package/dist/types/index.js +6 -0
  130. package/dist/utils.d.ts +2 -0
  131. package/dist/utils.js +37 -0
  132. package/package.json +79 -0
package/dist/doctor.js ADDED
@@ -0,0 +1,508 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, promises as fs } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { applyManagedEnv, readManifest } from './setup/paths.js';
5
+ import { spawnTool } from './setup/spawn-tool.js';
6
+ import { avdHomeDir, isAvdImageInstalled, readAvdSystemImage } from './setup/avd.js';
7
+ export { normalizeSysImagePath } from './setup/avd.js';
8
+ export async function runDoctorChecks() {
9
+ applyManagedEnv();
10
+ const checks = [];
11
+ checks.push({
12
+ name: 'Node.js 24.x–25.x',
13
+ status: isNodeVersionSupported(process.versions.node) ? 'ok' : 'error',
14
+ detail: isNodeVersionSupported(process.versions.node)
15
+ ? process.version
16
+ : `${process.version} — taqwright requires Node.js 24 or 25 (Node 26+ has a known bug)`,
17
+ });
18
+ const adb = await commandExists('adb');
19
+ checks.push({
20
+ name: 'adb (Android SDK)',
21
+ status: adb ? 'ok' : 'warn',
22
+ detail: adb ? 'on PATH' : 'not found — Android tests will not work',
23
+ });
24
+ checks.push(checkAndroidHome());
25
+ const avdImagesCheck = await checkManagedSdkAvdImages();
26
+ if (avdImagesCheck)
27
+ checks.push(avdImagesCheck);
28
+ if (process.platform === 'darwin') {
29
+ const xcrun = await commandExists('xcrun');
30
+ checks.push({
31
+ name: 'xcrun (Xcode CLT)',
32
+ status: xcrun ? 'ok' : 'warn',
33
+ detail: xcrun ? 'on PATH' : 'not found — iOS tests will not work',
34
+ });
35
+ if (xcrun) {
36
+ const devDir = (await readCommandOutput('xcode-select', ['-p']))?.trim();
37
+ if (!devDir) {
38
+ checks.push({
39
+ name: 'Xcode (full, for XCUITest)',
40
+ status: 'warn',
41
+ detail: 'xcode-select path not set — run `sudo xcode-select --switch ' +
42
+ '/Applications/Xcode.app/Contents/Developer`',
43
+ });
44
+ }
45
+ else if (/CommandLineTools/.test(devDir)) {
46
+ checks.push({
47
+ name: 'Xcode (full, for XCUITest)',
48
+ status: 'warn',
49
+ detail: `Command Line Tools only (${devDir}) — XCUITest needs full Xcode. ` +
50
+ 'Run `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer`',
51
+ });
52
+ }
53
+ else {
54
+ const xb = await readCommandOutput('xcodebuild', ['-version']);
55
+ const ver = xb?.match(/Xcode\s+([\d.]+)/);
56
+ if (ver) {
57
+ checks.push({
58
+ name: 'Xcode (full, for XCUITest)',
59
+ status: 'ok',
60
+ detail: `Xcode ${ver[1]} (${devDir})`,
61
+ });
62
+ }
63
+ else if (xb && /license/i.test(xb)) {
64
+ checks.push({
65
+ name: 'Xcode (full, for XCUITest)',
66
+ status: 'warn',
67
+ detail: 'Xcode license not accepted — run `sudo xcodebuild -license accept`',
68
+ });
69
+ }
70
+ else {
71
+ checks.push({
72
+ name: 'Xcode (full, for XCUITest)',
73
+ status: 'warn',
74
+ detail: `xcodebuild not usable (${devDir}) — check the Xcode install`,
75
+ });
76
+ }
77
+ }
78
+ }
79
+ const ffmpeg = await commandExists('ffmpeg');
80
+ checks.push({
81
+ name: 'ffmpeg (iOS-sim video)',
82
+ status: ffmpeg ? 'ok' : 'warn',
83
+ detail: ffmpeg
84
+ ? 'on PATH'
85
+ : 'not found — optional; only needed for iOS-simulator screen recording (video)',
86
+ });
87
+ const networksetup = await commandExists('networksetup');
88
+ checks.push({
89
+ name: 'networksetup (iOS-sim network capture)',
90
+ status: networksetup ? 'ok' : 'warn',
91
+ detail: networksetup
92
+ ? 'on PATH'
93
+ : 'not found — optional; only needed for iOS-simulator network capture (network)',
94
+ });
95
+ if (xcrun) {
96
+ const simJson = await readCommandOutput('xcrun', [
97
+ 'simctl',
98
+ 'list',
99
+ 'devices',
100
+ 'available',
101
+ '--json',
102
+ ]);
103
+ if (simJson === undefined) {
104
+ checks.push({
105
+ name: 'iOS simulator (WDA target)',
106
+ status: 'warn',
107
+ detail: 'could not query `xcrun simctl list devices available`',
108
+ });
109
+ }
110
+ else {
111
+ let count = 0;
112
+ let latest;
113
+ let latestScore = -1;
114
+ try {
115
+ const slice = simJson.slice(simJson.indexOf('{'), simJson.lastIndexOf('}') + 1);
116
+ const parsed = JSON.parse(slice);
117
+ for (const [runtime, entries] of Object.entries(parsed.devices ?? {})) {
118
+ const m = runtime.match(/iOS-(\d+)-(\d+)/);
119
+ if (!m)
120
+ continue;
121
+ const usable = (entries ?? []).filter((e) => e.isAvailable !== false);
122
+ if (usable.length === 0)
123
+ continue;
124
+ count += usable.length;
125
+ const score = Number(m[1]) * 1000 + Number(m[2]);
126
+ if (score > latestScore) {
127
+ latestScore = score;
128
+ latest = `${m[1]}.${m[2]}`;
129
+ }
130
+ }
131
+ }
132
+ catch {
133
+ }
134
+ if (count > 0) {
135
+ checks.push({
136
+ name: 'iOS simulator (WDA target)',
137
+ status: 'ok',
138
+ detail: `${count} available${latest ? ` (latest iOS ${latest})` : ''}`,
139
+ });
140
+ }
141
+ else {
142
+ checks.push({
143
+ name: 'iOS simulator (WDA target)',
144
+ status: 'warn',
145
+ detail: 'none available — WebDriverAgent has no build destination ' +
146
+ '(:8100 ECONNREFUSED). Install one: `xcodebuild -downloadPlatform iOS` ' +
147
+ '(Xcode → Settings → Components).',
148
+ });
149
+ }
150
+ }
151
+ }
152
+ }
153
+ const forgeOk = await canImport('node-forge');
154
+ checks.push({
155
+ name: 'node-forge (network capture)',
156
+ status: forgeOk ? 'ok' : 'warn',
157
+ detail: forgeOk ? 'loaded' : 'failed to import — reinstall dependencies (`npm install`)',
158
+ });
159
+ const java = await commandExists('java');
160
+ if (!java) {
161
+ checks.push({
162
+ name: 'java (JDK for UiAutomator2)',
163
+ status: 'warn',
164
+ detail: 'not found — Appium Android driver may fail',
165
+ });
166
+ }
167
+ else {
168
+ const jver = await readJavaVersion();
169
+ const level = jver ? classifyJdkVersion(jver) : 'unknown';
170
+ checks.push({
171
+ name: 'java (JDK for UiAutomator2)',
172
+ status: level === 'ok' ? 'ok' : 'warn',
173
+ detail: level === 'ok'
174
+ ? `on PATH (v${jver})`
175
+ : level === 'too-old'
176
+ ? `v${jver} is too old — Appium/UiAutomator2 + Android build-tools need JDK ${MIN_JDK_MAJOR}+`
177
+ : 'on PATH but version could not be read',
178
+ });
179
+ }
180
+ checks.push(checkJavaHome());
181
+ const appium = await commandExists('appium');
182
+ if (!appium) {
183
+ checks.push({
184
+ name: 'Appium (test server)',
185
+ status: 'warn',
186
+ detail: 'not found — install with `npm i -g appium@^3` then `appium driver install uiautomator2`',
187
+ });
188
+ }
189
+ else {
190
+ const version = await readCommandVersion('appium');
191
+ if (!version) {
192
+ checks.push({
193
+ name: 'Appium (test server)',
194
+ status: 'warn',
195
+ detail: 'on PATH but version could not be read',
196
+ });
197
+ }
198
+ else {
199
+ const level = classifyAppiumVersion(version);
200
+ if (level === 'recommended') {
201
+ checks.push({
202
+ name: 'Appium (test server)',
203
+ status: 'ok',
204
+ detail: `on PATH (v${version})`,
205
+ });
206
+ }
207
+ else if (level === 'best-effort') {
208
+ checks.push({
209
+ name: 'Appium (test server)',
210
+ status: 'warn',
211
+ detail: `Appium 2.x detected (v${version}) — best-effort, not officially tested. ` +
212
+ 'Upgrade for the supported path: `npm i -g appium@^3`.',
213
+ });
214
+ }
215
+ else {
216
+ checks.push({
217
+ name: 'Appium (test server)',
218
+ status: 'error',
219
+ detail: `v${version} is not supported — taqwright targets Appium 3.x ` +
220
+ '(2.x runs best-effort). Upgrade with `npm i -g appium@^3`.',
221
+ });
222
+ }
223
+ }
224
+ }
225
+ if (appium) {
226
+ const driverOut = await readCommandOutput('appium', ['driver', 'list', '--installed']);
227
+ if (driverOut === undefined) {
228
+ checks.push({
229
+ name: 'Appium drivers',
230
+ status: 'warn',
231
+ detail: 'could not query `appium driver list --installed`',
232
+ });
233
+ }
234
+ else {
235
+ const hasUia2 = /uiautomator2/.test(driverOut);
236
+ const hasXcui = /xcuitest/.test(driverOut);
237
+ if (!hasUia2 && !hasXcui) {
238
+ checks.push({
239
+ name: 'Appium drivers',
240
+ status: 'warn',
241
+ detail: 'no platform driver installed — `appium driver install uiautomator2` ' +
242
+ '(Android) and/or `appium driver install xcuitest` (iOS)',
243
+ });
244
+ }
245
+ else {
246
+ const installed = [hasUia2 ? 'uiautomator2' : undefined, hasXcui ? 'xcuitest' : undefined]
247
+ .filter(Boolean)
248
+ .join(', ');
249
+ const missing = [];
250
+ if (!hasUia2)
251
+ missing.push('uiautomator2 (Android) `appium driver install uiautomator2`');
252
+ if (!hasXcui)
253
+ missing.push('xcuitest (iOS) `appium driver install xcuitest`');
254
+ checks.push({
255
+ name: 'Appium drivers',
256
+ status: 'ok',
257
+ detail: missing.length ? `${installed}; not installed: ${missing.join('; ')}` : installed,
258
+ });
259
+ }
260
+ }
261
+ }
262
+ return checks;
263
+ }
264
+ function checkJavaHome() {
265
+ const name = 'JAVA_HOME (UiAutomator2 JDK)';
266
+ const home = process.env.JAVA_HOME;
267
+ if (!home) {
268
+ return {
269
+ name,
270
+ status: 'warn',
271
+ detail: "unset — Appium's UiAutomator2 driver may not find the JDK. Export " +
272
+ 'JAVA_HOME (e.g. `$(/usr/libexec/java_home -v 21)` on macOS) in the ' +
273
+ 'shell that launches taqwright.',
274
+ };
275
+ }
276
+ if (!existsSync(home)) {
277
+ return { name, status: 'warn', detail: `set to ${home} but that directory does not exist` };
278
+ }
279
+ const javaBin = path.join(home, 'bin', process.platform === 'win32' ? 'java.exe' : 'java');
280
+ if (!existsSync(javaBin)) {
281
+ return {
282
+ name,
283
+ status: 'warn',
284
+ detail: `set to ${home} but ${path.join('bin', 'java')} is missing — not a JDK home`,
285
+ };
286
+ }
287
+ return { name, status: 'ok', detail: `JAVA_HOME=${home}` };
288
+ }
289
+ function checkAndroidHome() {
290
+ const name = 'ANDROID_HOME (Appium adb lookup)';
291
+ const home = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
292
+ if (!home) {
293
+ return {
294
+ name,
295
+ status: 'warn',
296
+ detail: 'unset — Appium will not find adb. Export ANDROID_HOME (e.g. `~/Library/Android/sdk`) ' +
297
+ 'in the shell that launches taqwright.',
298
+ };
299
+ }
300
+ if (!existsSync(home)) {
301
+ return {
302
+ name,
303
+ status: 'warn',
304
+ detail: `set to ${home} but that directory does not exist`,
305
+ };
306
+ }
307
+ const adbBin = path.join(home, 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb');
308
+ if (!existsSync(adbBin)) {
309
+ return {
310
+ name,
311
+ status: 'warn',
312
+ detail: `set to ${home} but ${path.join('platform-tools', 'adb')} is missing — install Android SDK platform-tools`,
313
+ };
314
+ }
315
+ const source = process.env.ANDROID_HOME ? 'ANDROID_HOME' : 'ANDROID_SDK_ROOT';
316
+ return { name, status: 'ok', detail: `${source}=${home}` };
317
+ }
318
+ async function canImport(name) {
319
+ try {
320
+ await import(name);
321
+ return true;
322
+ }
323
+ catch {
324
+ return false;
325
+ }
326
+ }
327
+ async function checkManagedSdkAvdImages() {
328
+ if (!readManifest())
329
+ return undefined;
330
+ const androidHome = process.env.ANDROID_HOME;
331
+ if (!androidHome)
332
+ return undefined;
333
+ const avdHome = avdHomeDir();
334
+ if (!existsSync(avdHome))
335
+ return undefined;
336
+ let entries;
337
+ try {
338
+ entries = await fs.readdir(avdHome);
339
+ }
340
+ catch {
341
+ return undefined;
342
+ }
343
+ const avdNames = entries
344
+ .filter((name) => name.endsWith('.avd'))
345
+ .map((name) => name.slice(0, -'.avd'.length));
346
+ if (avdNames.length === 0)
347
+ return undefined;
348
+ const missing = [];
349
+ let total = 0;
350
+ for (const avdName of avdNames) {
351
+ const image = await readAvdSystemImage(avdName, avdHome);
352
+ if (image === undefined)
353
+ continue;
354
+ total++;
355
+ if (!isAvdImageInstalled(image, androidHome)) {
356
+ missing.push({ avd: avdName, image });
357
+ }
358
+ }
359
+ if (missing.length === 0) {
360
+ return {
361
+ name: 'Managed SDK AVD images',
362
+ status: 'ok',
363
+ detail: `${total} AVD${total === 1 ? '' : 's'} found, all system images present`,
364
+ };
365
+ }
366
+ const list = missing.map((m) => `${m.avd} → ${m.image}`).join('; ');
367
+ const sdkmanagerCmds = missing.map((m) => `sdkmanager "${m.image.replace(/\//g, ';')}"`);
368
+ return {
369
+ name: 'Managed SDK AVD images',
370
+ status: 'warn',
371
+ detail: `${missing.length} AVD${missing.length === 1 ? '' : 's'} reference a system image not in the managed SDK: ${list}. ` +
372
+ `taqwright auto-boots such an AVD against your system SDK when the image is present there; ` +
373
+ `otherwise fix one of: (a) install the missing image into the managed SDK — \`${sdkmanagerCmds[0]}\` ` +
374
+ `(uses \`${path.join(androidHome, 'cmdline-tools', 'latest', 'bin', 'sdkmanager')}\`); ` +
375
+ `(b) bypass the managed toolchain — \`rm ${path.join(path.dirname(androidHome), 'manifest.json')}\` ` +
376
+ `(falls back to your shell ANDROID_HOME); ` +
377
+ `(c) recreate the AVD against an image present in the managed SDK.`,
378
+ };
379
+ }
380
+ async function commandExists(name) {
381
+ return new Promise((resolve) => {
382
+ const child = spawn(process.platform === 'win32' ? 'where' : 'which', [name], {
383
+ stdio: 'ignore',
384
+ });
385
+ child.on('exit', (code) => resolve(code === 0));
386
+ child.on('error', () => resolve(false));
387
+ });
388
+ }
389
+ async function readCommandVersion(cmd) {
390
+ return new Promise((resolve) => {
391
+ const child = spawnTool(cmd, ['--version'], {
392
+ stdio: ['ignore', 'pipe', 'pipe'],
393
+ });
394
+ let out = '';
395
+ child.stdout?.on('data', (chunk) => {
396
+ out += String(chunk);
397
+ });
398
+ child.stderr?.on('data', (chunk) => {
399
+ out += String(chunk);
400
+ });
401
+ child.on('exit', () => {
402
+ const m = out.match(/(\d+)\.(\d+)\.(\d+)(?:-[\w.]+)?/);
403
+ resolve(m ? m[0] : undefined);
404
+ });
405
+ child.on('error', () => resolve(undefined));
406
+ });
407
+ }
408
+ async function readCommandOutput(cmd, args) {
409
+ return new Promise((resolve) => {
410
+ const child = spawnTool(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
411
+ let out = '';
412
+ child.stdout?.on('data', (chunk) => {
413
+ out += String(chunk);
414
+ });
415
+ child.stderr?.on('data', (chunk) => {
416
+ out += String(chunk);
417
+ });
418
+ child.on('exit', () => resolve(out));
419
+ child.on('error', () => resolve(undefined));
420
+ });
421
+ }
422
+ export function classifyAppiumVersion(version) {
423
+ const m = version.match(/^(\d+)\./);
424
+ if (!m)
425
+ return 'unsupported';
426
+ const major = Number(m[1]);
427
+ if (!Number.isFinite(major))
428
+ return 'unsupported';
429
+ if (major >= 3)
430
+ return 'recommended';
431
+ if (major === 2)
432
+ return 'best-effort';
433
+ return 'unsupported';
434
+ }
435
+ const MIN_JDK_MAJOR = 17;
436
+ export function classifyJdkVersion(version) {
437
+ const m = version.match(/(\d+)(?:\.(\d+))?/);
438
+ if (!m)
439
+ return 'unknown';
440
+ let major = Number(m[1]);
441
+ if (major === 1 && m[2])
442
+ major = Number(m[2]);
443
+ if (!Number.isFinite(major))
444
+ return 'unknown';
445
+ return major >= MIN_JDK_MAJOR ? 'ok' : 'too-old';
446
+ }
447
+ export function androidToolchainReady(s) {
448
+ return s.jdk === 'ok' && s.sdk && s.appium === 'recommended' && s.uiautomator2;
449
+ }
450
+ async function readJavaVersion() {
451
+ const out = await readCommandOutput('java', ['-version']);
452
+ return out?.match(/version "([^"]+)"/)?.[1];
453
+ }
454
+ export async function listAvds() {
455
+ try {
456
+ const entries = await fs.readdir(avdHomeDir());
457
+ return entries.filter((e) => e.endsWith('.ini')).map((e) => e.slice(0, -'.ini'.length));
458
+ }
459
+ catch {
460
+ return [];
461
+ }
462
+ }
463
+ export async function detectAndroidToolchain() {
464
+ applyManagedEnv();
465
+ const [jdkPresent, sdk, appiumPresent] = await Promise.all([
466
+ commandExists('java'),
467
+ commandExists('adb'),
468
+ commandExists('appium'),
469
+ ]);
470
+ let jdk = 'missing';
471
+ let jdkVersion;
472
+ if (jdkPresent) {
473
+ jdkVersion = await readJavaVersion();
474
+ jdk = jdkVersion ? classifyJdkVersion(jdkVersion) : 'unknown';
475
+ }
476
+ let appium = 'missing';
477
+ let appiumVersion;
478
+ let uiautomator2 = false;
479
+ if (appiumPresent) {
480
+ appiumVersion = await readCommandVersion('appium');
481
+ appium = appiumVersion ? classifyAppiumVersion(appiumVersion) : 'unsupported';
482
+ const drivers = await readCommandOutput('appium', ['driver', 'list', '--installed']);
483
+ uiautomator2 = drivers !== undefined && /uiautomator2/.test(drivers);
484
+ }
485
+ const avdNames = await listAvds();
486
+ return {
487
+ jdk,
488
+ jdkVersion,
489
+ sdk,
490
+ appium,
491
+ appiumVersion,
492
+ uiautomator2,
493
+ avd: avdNames.length > 0,
494
+ avdNames,
495
+ ready: androidToolchainReady({ jdk, sdk, appium, uiautomator2 }),
496
+ };
497
+ }
498
+ export function isAppiumVersionSupported(version) {
499
+ const level = classifyAppiumVersion(version);
500
+ return level === 'recommended' || level === 'best-effort';
501
+ }
502
+ export function isNodeVersionSupported(version) {
503
+ const m = version.match(/^v?(\d+)\./);
504
+ if (!m)
505
+ return false;
506
+ const major = Number(m[1]);
507
+ return Number.isFinite(major) && major >= 24 && major < 26;
508
+ }
@@ -0,0 +1,38 @@
1
+ import { expect as pwExpect } from '@playwright/test';
2
+ import { Locator } from './locator/index.js';
3
+ interface TimeoutOptions {
4
+ timeout?: number;
5
+ }
6
+ interface VisibleOptions extends TimeoutOptions {
7
+ visible?: boolean;
8
+ }
9
+ interface EnabledOptions extends TimeoutOptions {
10
+ enabled?: boolean;
11
+ }
12
+ interface CheckedOptions extends TimeoutOptions {
13
+ checked?: boolean;
14
+ }
15
+ interface EditableOptions extends TimeoutOptions {
16
+ editable?: boolean;
17
+ }
18
+ export interface MobileMatchers {
19
+ toBeVisible(options?: VisibleOptions): Promise<void>;
20
+ toBeHidden(options?: TimeoutOptions): Promise<void>;
21
+ toBeEnabled(options?: EnabledOptions): Promise<void>;
22
+ toBeDisabled(options?: TimeoutOptions): Promise<void>;
23
+ toBeChecked(options?: CheckedOptions): Promise<void>;
24
+ toBeEditable(options?: EditableOptions): Promise<void>;
25
+ toBeFocused(options?: TimeoutOptions): Promise<void>;
26
+ toBeAttached(options?: TimeoutOptions): Promise<void>;
27
+ toBeInViewport(options?: TimeoutOptions): Promise<void>;
28
+ toBeEmpty(options?: TimeoutOptions): Promise<void>;
29
+ toHaveText(expected: string | RegExp, options?: TimeoutOptions): Promise<void>;
30
+ toContainText(expected: string, options?: TimeoutOptions): Promise<void>;
31
+ toHaveValue(value: string | RegExp, options?: TimeoutOptions): Promise<void>;
32
+ toHaveCount(count: number, options?: TimeoutOptions): Promise<void>;
33
+ toHaveAttribute(name: string, value: string | RegExp, options?: TimeoutOptions): Promise<void>;
34
+ readonly not: MobileMatchers;
35
+ }
36
+ export type TaqwrightExpect = ((actual: Locator, message?: string) => MobileMatchers) & typeof pwExpect;
37
+ export declare const expect: TaqwrightExpect;
38
+ export {};
package/dist/expect.js ADDED
@@ -0,0 +1,96 @@
1
+ import { expect as pwExpect } from '@playwright/test';
2
+ import { Locator } from './locator/index.js';
3
+ const WEB_ONLY_MATCHERS = [
4
+ 'toHaveClass',
5
+ 'toContainClass',
6
+ 'toHaveCSS',
7
+ 'toHaveId',
8
+ 'toHaveJSProperty',
9
+ 'toHaveValues',
10
+ 'toHaveRole',
11
+ 'toHaveAccessibleName',
12
+ 'toHaveAccessibleDescription',
13
+ 'toHaveAccessibleErrorMessage',
14
+ 'toHaveScreenshot',
15
+ 'toMatchAriaSnapshot',
16
+ 'toHaveTitle',
17
+ 'toHaveURL',
18
+ 'toBeOK',
19
+ ];
20
+ const UNSUPPORTED_OPTION_KEYS = ['ignoreCase', 'useInnerText', 'ratio', 'indeterminate'];
21
+ function actionOpts(o) {
22
+ for (const key of UNSUPPORTED_OPTION_KEYS) {
23
+ if (o && key in o) {
24
+ throw new Error(`expect(locator): option "${key}" is not supported on mobile locators`);
25
+ }
26
+ }
27
+ return o?.timeout === undefined ? undefined : { timeout: o.timeout };
28
+ }
29
+ function makeMobileMatchers(loc, negated, message) {
30
+ const run = async (fn) => {
31
+ try {
32
+ await fn();
33
+ }
34
+ catch (err) {
35
+ if (message) {
36
+ const detail = err instanceof Error ? err.message : String(err);
37
+ throw new Error(`${message}\n${detail}`, { cause: err });
38
+ }
39
+ throw err;
40
+ }
41
+ };
42
+ const unsupportedNot = (matcher) => {
43
+ throw new Error(`expect(locator).not.${matcher}() is not supported on mobile locators — ` +
44
+ `use the positive form or locator.assert*() directly`);
45
+ };
46
+ const matchers = {
47
+ toBeVisible: (o) => {
48
+ const want = (o?.visible ?? true) !== negated;
49
+ return run(() => (want ? loc.assertVisible(actionOpts(o)) : loc.assertHidden(actionOpts(o))));
50
+ },
51
+ toBeHidden: (o) => run(() => (negated ? loc.assertVisible(actionOpts(o)) : loc.assertHidden(actionOpts(o)))),
52
+ toBeEnabled: (o) => {
53
+ const want = (o?.enabled ?? true) !== negated;
54
+ return run(() => want ? loc.assertEnabled(actionOpts(o)) : loc.assertDisabled(actionOpts(o)));
55
+ },
56
+ toBeDisabled: (o) => run(() => (negated ? loc.assertEnabled(actionOpts(o)) : loc.assertDisabled(actionOpts(o)))),
57
+ toBeChecked: (o) => {
58
+ const want = (o?.checked ?? true) !== negated;
59
+ return run(() => want ? loc.assertChecked(actionOpts(o)) : loc.assertUnchecked(actionOpts(o)));
60
+ },
61
+ toBeEditable: (o) => {
62
+ const want = (o?.editable ?? true) !== negated;
63
+ return run(() => want ? loc.assertEditable(actionOpts(o)) : loc.assertReadonly(actionOpts(o)));
64
+ },
65
+ toBeFocused: (o) => negated ? unsupportedNot('toBeFocused') : run(() => loc.assertFocused(actionOpts(o))),
66
+ toBeAttached: (o) => negated ? unsupportedNot('toBeAttached') : run(() => loc.assertAttached(actionOpts(o))),
67
+ toBeInViewport: (o) => negated ? unsupportedNot('toBeInViewport') : run(() => loc.assertInViewport(actionOpts(o))),
68
+ toBeEmpty: (o) => negated ? unsupportedNot('toBeEmpty') : run(() => loc.assertEmpty(actionOpts(o))),
69
+ toHaveText: (expected, o) => negated ? unsupportedNot('toHaveText') : run(() => loc.assertText(expected, actionOpts(o))),
70
+ toContainText: (expected, o) => negated
71
+ ? unsupportedNot('toContainText')
72
+ : run(() => loc.assertContainsText(expected, actionOpts(o))),
73
+ toHaveValue: (value, o) => negated ? unsupportedNot('toHaveValue') : run(() => loc.assertValue(value, actionOpts(o))),
74
+ toHaveCount: (count, o) => negated ? unsupportedNot('toHaveCount') : run(() => loc.assertCount(count, actionOpts(o))),
75
+ toHaveAttribute: (name, value, o) => negated
76
+ ? unsupportedNot('toHaveAttribute')
77
+ : run(() => loc.assertAttribute(name, value, actionOpts(o))),
78
+ get not() {
79
+ return makeMobileMatchers(loc, !negated, message);
80
+ },
81
+ };
82
+ for (const name of WEB_ONLY_MATCHERS) {
83
+ matchers[name] = () => {
84
+ throw new Error(`${name}() is not supported on mobile locators`);
85
+ };
86
+ }
87
+ return matchers;
88
+ }
89
+ const expectImpl = ((actual, message) => {
90
+ if (actual instanceof Locator) {
91
+ return makeMobileMatchers(actual, false, message);
92
+ }
93
+ return pwExpect(actual, message);
94
+ });
95
+ Object.assign(expectImpl, pwExpect);
96
+ export const expect = expectImpl;
@@ -0,0 +1,2 @@
1
+ export type ArtifactMode = 'off' | 'on' | 'on-failure' | 'retain-on-failure';
2
+ export declare function shouldRetainArtifact(mode: ArtifactMode, failed: boolean): boolean;
@@ -0,0 +1,7 @@
1
+ export function shouldRetainArtifact(mode, failed) {
2
+ if (mode === 'on')
3
+ return true;
4
+ if (mode === 'on-failure' || mode === 'retain-on-failure')
5
+ return failed;
6
+ return false;
7
+ }
@@ -0,0 +1,15 @@
1
+ import type { Client as WebDriverClient } from 'webdriver';
2
+ import { Mobile } from '../mobile/index.js';
3
+ import { type DeviceProvider, type TaqwrightUseOptions } from '../types/index.js';
4
+ import { type NetworkProxyHandle } from '../network/index.js';
5
+ interface TaqwrightFixtures {
6
+ mobile: Mobile;
7
+ rawDriver: WebDriverClient;
8
+ networkProxy: NetworkProxyHandle | null;
9
+ }
10
+ interface TaqwrightWorkerFixtures {
11
+ taqwrightUse: TaqwrightUseOptions;
12
+ deviceProvider: DeviceProvider | null;
13
+ }
14
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & TaqwrightFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & TaqwrightWorkerFixtures>;
15
+ export {};