@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
@@ -0,0 +1,5 @@
1
+ export type AndroidDeviceType = 'userdebug-emulator' | 'play-emulator' | 'real-device' | 'unknown';
2
+ export declare function detectAndroidDeviceType(udid: string): Promise<AndroidDeviceType>;
3
+ export declare function installSystemCa(udid: string, certPemPath: string, hashName: string): Promise<boolean>;
4
+ export declare function setHttpProxy(udid: string, host: string, port: number): Promise<void>;
5
+ export declare function clearHttpProxy(udid: string): Promise<void>;
@@ -0,0 +1,87 @@
1
+ import { exec } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execP = promisify(exec);
4
+ export async function detectAndroidDeviceType(udid) {
5
+ const qemu = (await getProp(udid, 'ro.boot.qemu')) || (await getProp(udid, 'ro.kernel.qemu'));
6
+ const tags = await getProp(udid, 'ro.build.tags');
7
+ if (qemu !== '1') {
8
+ const chars = await getProp(udid, 'ro.build.characteristics');
9
+ if (!/emulator/i.test(chars))
10
+ return 'real-device';
11
+ }
12
+ if (tags === 'release-keys')
13
+ return 'play-emulator';
14
+ if (tags === 'dev-keys' || tags === 'test-keys')
15
+ return 'userdebug-emulator';
16
+ return 'unknown';
17
+ }
18
+ export async function installSystemCa(udid, certPemPath, hashName) {
19
+ try {
20
+ await adb(udid, 'root');
21
+ await sleep(500);
22
+ await waitForDevice(udid);
23
+ let remountOut = '';
24
+ try {
25
+ remountOut = (await adb(udid, 'remount')).stdout + (await adb(udid, 'remount')).stderr;
26
+ }
27
+ catch (e) {
28
+ remountOut = String(e.stderr ?? e);
29
+ }
30
+ if (/verity/i.test(remountOut)) {
31
+ await adb(udid, 'disable-verity').catch(() => undefined);
32
+ await adb(udid, 'reboot').catch(() => undefined);
33
+ await waitForDevice(udid, 60_000);
34
+ await adb(udid, 'root').catch(() => undefined);
35
+ await sleep(500);
36
+ await waitForDevice(udid);
37
+ await adb(udid, 'remount');
38
+ }
39
+ const dest = `/system/etc/security/cacerts/${hashName}.0`;
40
+ await adb(udid, 'push', certPemPath, dest);
41
+ await adb(udid, 'shell', `chmod 644 ${dest}`);
42
+ return true;
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ export async function setHttpProxy(udid, host, port) {
49
+ await adb(udid, 'shell', `settings put global http_proxy ${host}:${port}`);
50
+ }
51
+ export async function clearHttpProxy(udid) {
52
+ await adb(udid, 'shell', 'settings put global http_proxy :0');
53
+ }
54
+ async function getProp(udid, name) {
55
+ try {
56
+ const { stdout } = await adb(udid, 'shell', `getprop ${name}`);
57
+ return stdout.trim();
58
+ }
59
+ catch {
60
+ return '';
61
+ }
62
+ }
63
+ async function waitForDevice(udid, timeoutMs = 20_000) {
64
+ const deadline = Date.now() + timeoutMs;
65
+ while (Date.now() < deadline) {
66
+ try {
67
+ const { stdout } = await execP(`adb -s ${shellQuote(udid)} get-state`, { timeout: 5_000 });
68
+ if (stdout.trim() === 'device')
69
+ return;
70
+ }
71
+ catch {
72
+ }
73
+ await sleep(500);
74
+ }
75
+ }
76
+ async function adb(udid, ...args) {
77
+ const cmd = ['adb', '-s', shellQuote(udid), ...args.map(shellQuote)].join(' ');
78
+ return execP(cmd, { maxBuffer: 16 * 1024 * 1024 });
79
+ }
80
+ function shellQuote(s) {
81
+ if (/^[A-Za-z0-9_\-./=:,@%+]+$/.test(s))
82
+ return s;
83
+ return `'${s.replace(/'/g, `'\\''`)}'`;
84
+ }
85
+ function sleep(ms) {
86
+ return new Promise((resolve) => setTimeout(resolve, ms));
87
+ }
@@ -0,0 +1,10 @@
1
+ export interface CaBundle {
2
+ certPem: string;
3
+ certPemPath: string;
4
+ androidHashName: string;
5
+ signLeaf(host: string): {
6
+ keyPem: string;
7
+ certPem: string;
8
+ };
9
+ }
10
+ export declare function ensureCa(): Promise<CaBundle>;
@@ -0,0 +1,136 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import forge from 'node-forge';
5
+ const CA_DIR = path.join(os.homedir(), '.taqwright', 'network-ca');
6
+ const CA_CERT_PATH = path.join(CA_DIR, 'ca.pem');
7
+ const CA_KEY_PATH = path.join(CA_DIR, 'ca.key');
8
+ const LEAF_CACHE_MAX = 256;
9
+ export async function ensureCa() {
10
+ await fs.mkdir(CA_DIR, { recursive: true });
11
+ let certPem;
12
+ let keyPem;
13
+ try {
14
+ certPem = await fs.readFile(CA_CERT_PATH, 'utf-8');
15
+ keyPem = await fs.readFile(CA_KEY_PATH, 'utf-8');
16
+ }
17
+ catch {
18
+ }
19
+ let caCert;
20
+ let caKey;
21
+ if (certPem && keyPem && isStillValid(certPem)) {
22
+ caCert = forge.pki.certificateFromPem(certPem);
23
+ caKey = forge.pki.privateKeyFromPem(keyPem);
24
+ }
25
+ else {
26
+ const generated = generateCa();
27
+ caCert = generated.cert;
28
+ caKey = generated.key;
29
+ certPem = forge.pki.certificateToPem(caCert);
30
+ keyPem = forge.pki.privateKeyToPem(caKey);
31
+ await fs.writeFile(CA_CERT_PATH, certPem, { mode: 0o644 });
32
+ await fs.writeFile(CA_KEY_PATH, keyPem, { mode: 0o600 });
33
+ }
34
+ const androidHashName = androidSubjectHashOld(caCert);
35
+ const leafCache = new Map();
36
+ return {
37
+ certPem,
38
+ certPemPath: CA_CERT_PATH,
39
+ androidHashName,
40
+ signLeaf(host) {
41
+ const cached = leafCache.get(host);
42
+ if (cached) {
43
+ leafCache.delete(host);
44
+ leafCache.set(host, cached);
45
+ return cached;
46
+ }
47
+ const { cert, key } = generateLeaf(host, caCert, caKey);
48
+ const pair = {
49
+ keyPem: forge.pki.privateKeyToPem(key),
50
+ certPem: forge.pki.certificateToPem(cert),
51
+ };
52
+ leafCache.set(host, pair);
53
+ if (leafCache.size > LEAF_CACHE_MAX) {
54
+ const oldest = leafCache.keys().next().value;
55
+ if (oldest)
56
+ leafCache.delete(oldest);
57
+ }
58
+ return pair;
59
+ },
60
+ };
61
+ }
62
+ function isStillValid(pem) {
63
+ try {
64
+ const cert = forge.pki.certificateFromPem(pem);
65
+ const now = Date.now();
66
+ const thirtyDays = 30 * 24 * 3600 * 1000;
67
+ return cert.validity.notAfter.getTime() - now > thirtyDays;
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ }
73
+ function generateCa() {
74
+ const keys = forge.pki.rsa.generateKeyPair(2048);
75
+ const cert = forge.pki.createCertificate();
76
+ cert.publicKey = keys.publicKey;
77
+ cert.serialNumber = randomSerial();
78
+ cert.validity.notBefore = new Date();
79
+ cert.validity.notAfter = new Date();
80
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10);
81
+ const attrs = [
82
+ { name: 'commonName', value: 'taqwright network capture CA' },
83
+ { name: 'organizationName', value: 'taqwright' },
84
+ { name: 'organizationalUnitName', value: 'network' },
85
+ ];
86
+ cert.setSubject(attrs);
87
+ cert.setIssuer(attrs);
88
+ cert.setExtensions([
89
+ { name: 'basicConstraints', cA: true, critical: true },
90
+ { name: 'keyUsage', keyCertSign: true, cRLSign: true, critical: true },
91
+ { name: 'subjectKeyIdentifier' },
92
+ ]);
93
+ cert.sign(keys.privateKey, forge.md.sha256.create());
94
+ return { cert, key: keys.privateKey };
95
+ }
96
+ function generateLeaf(host, caCert, caKey) {
97
+ const keys = forge.pki.rsa.generateKeyPair(2048);
98
+ const cert = forge.pki.createCertificate();
99
+ cert.publicKey = keys.publicKey;
100
+ cert.serialNumber = randomSerial();
101
+ cert.validity.notBefore = new Date(Date.now() - 24 * 3600 * 1000);
102
+ cert.validity.notAfter = new Date();
103
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 2);
104
+ cert.setSubject([{ name: 'commonName', value: host }]);
105
+ cert.setIssuer(caCert.subject.attributes);
106
+ const altNames = isIp(host) ? [{ type: 7, ip: host }] : [{ type: 2, value: host }];
107
+ cert.setExtensions([
108
+ { name: 'basicConstraints', cA: false },
109
+ { name: 'keyUsage', digitalSignature: true, keyEncipherment: true, critical: true },
110
+ { name: 'extKeyUsage', serverAuth: true, clientAuth: true },
111
+ { name: 'subjectAltName', altNames },
112
+ ]);
113
+ cert.sign(caKey, forge.md.sha256.create());
114
+ return { cert, key: keys.privateKey };
115
+ }
116
+ function randomSerial() {
117
+ const bytes = forge.random.getBytesSync(16);
118
+ let hex = forge.util.bytesToHex(bytes);
119
+ if (parseInt(hex[0], 16) > 7)
120
+ hex = '0' + hex.slice(1);
121
+ return hex;
122
+ }
123
+ function isIp(host) {
124
+ return /^[\d.]+$/.test(host) || host.includes(':');
125
+ }
126
+ function androidSubjectHashOld(cert) {
127
+ const der = forge.asn1.toDer(forge.pki.distinguishedNameToAsn1(cert.subject)).getBytes();
128
+ const md = forge.md.md5.create();
129
+ md.update(der);
130
+ const digestHex = md.digest().toHex();
131
+ const b0 = digestHex.slice(0, 2);
132
+ const b1 = digestHex.slice(2, 4);
133
+ const b2 = digestHex.slice(4, 6);
134
+ const b3 = digestHex.slice(6, 8);
135
+ return `${b3}${b2}${b1}${b0}`;
136
+ }
@@ -0,0 +1,90 @@
1
+ export interface HarHeader {
2
+ name: string;
3
+ value: string;
4
+ }
5
+ export interface HarRequestInit {
6
+ method: string;
7
+ url: string;
8
+ httpVersion: string;
9
+ headers: HarHeader[];
10
+ bodyBytes?: number;
11
+ bodyText?: string;
12
+ startedAt: Date;
13
+ }
14
+ export interface HarResponseInit {
15
+ status: number;
16
+ statusText: string;
17
+ httpVersion: string;
18
+ headers: HarHeader[];
19
+ mimeType: string;
20
+ bodyBytes?: number;
21
+ bodyText?: string;
22
+ endedAt: Date;
23
+ }
24
+ export interface HarEntryHandle {
25
+ id: number;
26
+ onResponse(r: HarResponseInit): void;
27
+ onError(err: string): void;
28
+ }
29
+ export interface HarLog {
30
+ log: {
31
+ version: '1.2';
32
+ creator: {
33
+ name: string;
34
+ version: string;
35
+ };
36
+ comment?: string;
37
+ entries: HarEntry[];
38
+ };
39
+ }
40
+ interface HarEntry {
41
+ startedDateTime: string;
42
+ time: number;
43
+ request: {
44
+ method: string;
45
+ url: string;
46
+ httpVersion: string;
47
+ headers: HarHeader[];
48
+ queryString: HarHeader[];
49
+ cookies: HarHeader[];
50
+ headersSize: number;
51
+ bodySize: number;
52
+ postData?: {
53
+ mimeType: string;
54
+ text: string;
55
+ };
56
+ };
57
+ response: {
58
+ status: number;
59
+ statusText: string;
60
+ httpVersion: string;
61
+ headers: HarHeader[];
62
+ cookies: HarHeader[];
63
+ content: {
64
+ size: number;
65
+ mimeType: string;
66
+ text?: string;
67
+ };
68
+ redirectURL: string;
69
+ headersSize: number;
70
+ bodySize: number;
71
+ };
72
+ cache: Record<string, never>;
73
+ timings: {
74
+ send: number;
75
+ wait: number;
76
+ receive: number;
77
+ };
78
+ comment?: string;
79
+ _error?: string;
80
+ }
81
+ export interface HarBuilder {
82
+ startEntry(req: HarRequestInit): HarEntryHandle;
83
+ addComment(line: string): void;
84
+ toJson(): HarLog;
85
+ }
86
+ export declare function createHarBuilder(meta: {
87
+ creator: string;
88
+ version?: string;
89
+ }): HarBuilder;
90
+ export {};
@@ -0,0 +1,101 @@
1
+ import { URL } from 'node:url';
2
+ export function createHarBuilder(meta) {
3
+ const entries = [];
4
+ const comments = [];
5
+ let nextId = 0;
6
+ return {
7
+ startEntry(req) {
8
+ const id = nextId++;
9
+ const entry = {
10
+ startedDateTime: req.startedAt.toISOString(),
11
+ time: 0,
12
+ request: {
13
+ method: req.method,
14
+ url: req.url,
15
+ httpVersion: req.httpVersion,
16
+ headers: req.headers,
17
+ queryString: parseQuery(req.url),
18
+ cookies: [],
19
+ headersSize: -1,
20
+ bodySize: req.bodyBytes ?? -1,
21
+ ...(req.bodyText !== undefined
22
+ ? {
23
+ postData: {
24
+ mimeType: headerValue(req.headers, 'content-type') ?? 'application/octet-stream',
25
+ text: req.bodyText,
26
+ },
27
+ }
28
+ : {}),
29
+ },
30
+ response: {
31
+ status: 0,
32
+ statusText: '',
33
+ httpVersion: '',
34
+ headers: [],
35
+ cookies: [],
36
+ content: { size: 0, mimeType: '' },
37
+ redirectURL: '',
38
+ headersSize: -1,
39
+ bodySize: -1,
40
+ },
41
+ cache: {},
42
+ timings: { send: 0, wait: -1, receive: -1 },
43
+ };
44
+ entries.push(entry);
45
+ const startedAt = req.startedAt.getTime();
46
+ return {
47
+ id,
48
+ onResponse(r) {
49
+ entry.time = Math.max(0, r.endedAt.getTime() - startedAt);
50
+ entry.response.status = r.status;
51
+ entry.response.statusText = r.statusText;
52
+ entry.response.httpVersion = r.httpVersion;
53
+ entry.response.headers = r.headers;
54
+ entry.response.content.mimeType = r.mimeType;
55
+ entry.response.content.size = r.bodyBytes ?? 0;
56
+ if (r.bodyText !== undefined)
57
+ entry.response.content.text = r.bodyText;
58
+ entry.response.bodySize = r.bodyBytes ?? -1;
59
+ entry.response.redirectURL = headerValue(r.headers, 'location') ?? '';
60
+ entry.timings.wait = entry.time;
61
+ },
62
+ onError(err) {
63
+ entry._error = err;
64
+ },
65
+ };
66
+ },
67
+ addComment(line) {
68
+ comments.push(line);
69
+ },
70
+ toJson() {
71
+ return {
72
+ log: {
73
+ version: '1.2',
74
+ creator: { name: meta.creator, version: meta.version ?? '0' },
75
+ ...(comments.length ? { comment: comments.join('\n') } : {}),
76
+ entries,
77
+ },
78
+ };
79
+ },
80
+ };
81
+ }
82
+ function parseQuery(url) {
83
+ try {
84
+ const u = new URL(url);
85
+ const out = [];
86
+ for (const [name, value] of u.searchParams)
87
+ out.push({ name, value });
88
+ return out;
89
+ }
90
+ catch {
91
+ return [];
92
+ }
93
+ }
94
+ function headerValue(headers, name) {
95
+ const lower = name.toLowerCase();
96
+ for (const h of headers) {
97
+ if (h.name.toLowerCase() === lower)
98
+ return h.value;
99
+ }
100
+ return undefined;
101
+ }
@@ -0,0 +1,16 @@
1
+ export interface HostProxyState {
2
+ service: string;
3
+ web: {
4
+ enabled: boolean;
5
+ host: string;
6
+ port: string;
7
+ };
8
+ secure: {
9
+ enabled: boolean;
10
+ host: string;
11
+ port: string;
12
+ };
13
+ }
14
+ export declare function snapshotProxy(): Promise<HostProxyState | undefined>;
15
+ export declare function applyProxy(state: HostProxyState, host: string, port: number): Promise<void>;
16
+ export declare function restoreProxy(): Promise<void>;
@@ -0,0 +1,134 @@
1
+ import { exec, execSync } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execP = promisify(exec);
4
+ let pending;
5
+ let handlersRegistered = false;
6
+ export async function snapshotProxy() {
7
+ const service = await activeNetworkService();
8
+ if (!service)
9
+ return undefined;
10
+ const web = await readProxy('-getwebproxy', service);
11
+ const secure = await readProxy('-getsecurewebproxy', service);
12
+ return { service, web, secure };
13
+ }
14
+ export async function applyProxy(state, host, port) {
15
+ ensureHandlersRegistered();
16
+ if (!pending) {
17
+ pending = { state, appliedWeb: false, appliedSecure: false };
18
+ }
19
+ try {
20
+ await execP(`networksetup -setwebproxy ${shellQuote(state.service)} ${host} ${port}`);
21
+ pending.appliedWeb = true;
22
+ }
23
+ catch {
24
+ }
25
+ try {
26
+ await execP(`networksetup -setsecurewebproxy ${shellQuote(state.service)} ${host} ${port}`);
27
+ pending.appliedSecure = true;
28
+ }
29
+ catch {
30
+ }
31
+ }
32
+ export async function restoreProxy() {
33
+ if (!pending)
34
+ return;
35
+ const { state, appliedWeb, appliedSecure } = pending;
36
+ pending = undefined;
37
+ if (appliedWeb) {
38
+ if (state.web.enabled && state.web.host && state.web.port) {
39
+ await execP(`networksetup -setwebproxy ${shellQuote(state.service)} ${state.web.host} ${state.web.port}`).catch(() => undefined);
40
+ }
41
+ else {
42
+ await execP(`networksetup -setwebproxystate ${shellQuote(state.service)} off`).catch(() => undefined);
43
+ }
44
+ }
45
+ if (appliedSecure) {
46
+ if (state.secure.enabled && state.secure.host && state.secure.port) {
47
+ await execP(`networksetup -setsecurewebproxy ${shellQuote(state.service)} ${state.secure.host} ${state.secure.port}`).catch(() => undefined);
48
+ }
49
+ else {
50
+ await execP(`networksetup -setsecurewebproxystate ${shellQuote(state.service)} off`).catch(() => undefined);
51
+ }
52
+ }
53
+ }
54
+ function restoreProxySync() {
55
+ if (!pending)
56
+ return;
57
+ const { state, appliedWeb, appliedSecure } = pending;
58
+ pending = undefined;
59
+ try {
60
+ if (appliedWeb) {
61
+ if (state.web.enabled && state.web.host && state.web.port) {
62
+ execSync(`networksetup -setwebproxy ${shellQuote(state.service)} ${state.web.host} ${state.web.port}`, { stdio: 'ignore' });
63
+ }
64
+ else {
65
+ execSync(`networksetup -setwebproxystate ${shellQuote(state.service)} off`, {
66
+ stdio: 'ignore',
67
+ });
68
+ }
69
+ }
70
+ if (appliedSecure) {
71
+ if (state.secure.enabled && state.secure.host && state.secure.port) {
72
+ execSync(`networksetup -setsecurewebproxy ${shellQuote(state.service)} ${state.secure.host} ${state.secure.port}`, { stdio: 'ignore' });
73
+ }
74
+ else {
75
+ execSync(`networksetup -setsecurewebproxystate ${shellQuote(state.service)} off`, {
76
+ stdio: 'ignore',
77
+ });
78
+ }
79
+ }
80
+ }
81
+ catch {
82
+ }
83
+ }
84
+ function ensureHandlersRegistered() {
85
+ if (handlersRegistered)
86
+ return;
87
+ handlersRegistered = true;
88
+ process.on('exit', restoreProxySync);
89
+ const onSignal = (signal) => {
90
+ restoreProxySync();
91
+ process.kill(process.pid, signal);
92
+ };
93
+ process.once('SIGINT', onSignal);
94
+ process.once('SIGTERM', onSignal);
95
+ process.once('SIGHUP', onSignal);
96
+ process.on('uncaughtException', (err) => {
97
+ restoreProxySync();
98
+ setImmediate(() => {
99
+ throw err;
100
+ });
101
+ });
102
+ }
103
+ async function activeNetworkService() {
104
+ try {
105
+ const { stdout } = await execP('networksetup -listallnetworkservices');
106
+ const services = stdout
107
+ .split('\n')
108
+ .filter((l) => l && !/asterisk denotes/i.test(l) && !l.startsWith('*'));
109
+ const preferred = services.find((s) => /wi-?fi/i.test(s)) ??
110
+ services.find((s) => /ethernet|usb|thunderbolt/i.test(s)) ??
111
+ services[0];
112
+ return preferred?.trim();
113
+ }
114
+ catch {
115
+ return 'Wi-Fi';
116
+ }
117
+ }
118
+ async function readProxy(kind, service) {
119
+ try {
120
+ const { stdout } = await execP(`networksetup ${kind} ${shellQuote(service)}`);
121
+ const enabled = /^Enabled:\s*Yes/im.test(stdout);
122
+ const host = stdout.match(/^Server:\s*(.*)$/im)?.[1]?.trim() ?? '';
123
+ const port = stdout.match(/^Port:\s*(\d+)/im)?.[1]?.trim() ?? '';
124
+ return { enabled, host, port };
125
+ }
126
+ catch {
127
+ return { enabled: false, host: '', port: '' };
128
+ }
129
+ }
130
+ function shellQuote(s) {
131
+ if (/^[A-Za-z0-9_\-./=:,@%+]+$/.test(s))
132
+ return s;
133
+ return `'${s.replace(/'/g, `'\\''`)}'`;
134
+ }
@@ -0,0 +1,26 @@
1
+ import type { ProxyHandle } from './proxy.js';
2
+ import { type CaBundle } from './ca.js';
3
+ import { type HostProxyState } from './host-proxy.js';
4
+ import { Platform } from '../types/index.js';
5
+ export interface NetworkProxyHandle {
6
+ port: number;
7
+ flush(): Promise<object>;
8
+ attemptLog: string[];
9
+ _proxy: ProxyHandle;
10
+ _ca: CaBundle;
11
+ _platform: Platform;
12
+ _hostProxyState?: HostProxyState;
13
+ _configuredUdid?: string;
14
+ _configuredAndroid?: boolean;
15
+ }
16
+ export declare function prepareNetworkProxy(opts: {
17
+ platform: Platform;
18
+ parallelIndex: number;
19
+ }): Promise<NetworkProxyHandle>;
20
+ export declare function configureDeviceForCapture(handle: NetworkProxyHandle, opts: {
21
+ udid: string | undefined;
22
+ platform: Platform;
23
+ }): Promise<void>;
24
+ export declare function teardownDeviceCapture(handle: NetworkProxyHandle): Promise<void>;
25
+ export declare function teardownNetworkProxy(handle: NetworkProxyHandle): Promise<void>;
26
+ export declare function extractUdid(driverCapabilities: unknown): string | undefined;