@le-space/playwright 0.6.34 → 0.6.36
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/cleanup-cli.js +61 -4
- package/index.d.ts +51 -1
- package/index.js +112 -12
- package/package.json +3 -3
- package/chunk-YAODEYKV.js +0 -66
package/cleanup-cli.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
resolveAlephApiHosts,
|
|
4
|
-
waitForAlephInstanceDeletion
|
|
5
|
-
} from "./chunk-YAODEYKV.js";
|
|
6
2
|
|
|
7
3
|
// src/cleanup-cli.ts
|
|
8
4
|
import { createHash } from "crypto";
|
|
@@ -10,6 +6,67 @@ import { appendFile, mkdir, writeFile } from "fs/promises";
|
|
|
10
6
|
import { dirname } from "path";
|
|
11
7
|
import process from "process";
|
|
12
8
|
import { eraseInstanceOnCrn, forgetAlephMessages } from "@le-space/core";
|
|
9
|
+
|
|
10
|
+
// src/aleph-instance.ts
|
|
11
|
+
var SUPPORTED_ALEPH_API_HOSTS = ["https://api2.aleph.im", "https://api.aleph.im"];
|
|
12
|
+
var DEFAULT_ALEPH_SCHEDULER_URL = "https://scheduler.api.aleph.cloud";
|
|
13
|
+
var API3_HOST_PATTERN = /(^|\.)api3\.aleph\.im$/iu;
|
|
14
|
+
function delay(ms) {
|
|
15
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
|
+
}
|
|
17
|
+
function normalizeApiOrigin(value) {
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(value);
|
|
20
|
+
if (API3_HOST_PATTERN.test(url.hostname)) return null;
|
|
21
|
+
return url.origin;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function resolveAlephApiHosts(candidates) {
|
|
27
|
+
const allowed = new Set((candidates ?? SUPPORTED_ALEPH_API_HOSTS).map(normalizeApiOrigin).filter((value) => value != null));
|
|
28
|
+
const selected = SUPPORTED_ALEPH_API_HOSTS.filter((host) => allowed.has(host));
|
|
29
|
+
return selected.length > 0 ? [...selected] : [...SUPPORTED_ALEPH_API_HOSTS];
|
|
30
|
+
}
|
|
31
|
+
async function waitForAlephInstanceDeletion(options) {
|
|
32
|
+
const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
33
|
+
if (!fetchImpl) throw new Error("A fetch implementation is required for cleanup verification");
|
|
34
|
+
const deadline = Date.now() + (options.timeoutMs ?? 5 * 6e4);
|
|
35
|
+
const hosts = resolveAlephApiHosts(options.apiHosts);
|
|
36
|
+
const schedulerUrl = new URL(`/api/v0/allocation/${options.instanceHash}`, options.schedulerUrl ?? DEFAULT_ALEPH_SCHEDULER_URL);
|
|
37
|
+
let lastSummary = "Deletion has not been observed yet.";
|
|
38
|
+
while (Date.now() < deadline) {
|
|
39
|
+
const observations = [];
|
|
40
|
+
let replicasForgotten = true;
|
|
41
|
+
for (const apiHost of hosts) {
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetchImpl(new URL(`/api/v0/messages/${options.instanceHash}`, apiHost), { cache: "no-cache" });
|
|
44
|
+
const payload = await response.json().catch(() => null);
|
|
45
|
+
const forgotten = payload?.status === "forgotten" || Boolean(payload?.forgotten_by?.length);
|
|
46
|
+
replicasForgotten &&= forgotten;
|
|
47
|
+
observations.push(`${apiHost}: ${forgotten ? "forgotten" : payload?.status ?? `HTTP ${response.status}`}`);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
replicasForgotten = false;
|
|
50
|
+
observations.push(`${apiHost}: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
let unallocated = false;
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetchImpl(schedulerUrl, { cache: "no-cache" });
|
|
56
|
+
const payload = await response.json().catch(() => null);
|
|
57
|
+
unallocated = response.status === 404 || payload?.error === "VM is not allocated to any node";
|
|
58
|
+
observations.push(`scheduler: ${unallocated ? "unallocated" : `HTTP ${response.status}`}`);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
observations.push(`scheduler: ${error instanceof Error ? error.message : String(error)}`);
|
|
61
|
+
}
|
|
62
|
+
lastSummary = observations.join("; ");
|
|
63
|
+
if (replicasForgotten && unallocated) return lastSummary;
|
|
64
|
+
await delay(options.pollIntervalMs ?? 2e3);
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`Aleph INSTANCE ${options.instanceHash} was not deleted within ${options.timeoutMs ?? 5 * 6e4}ms: ${lastSummary}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/cleanup-cli.ts
|
|
13
70
|
function parseCleanupCliArgs(argv, env) {
|
|
14
71
|
const flags = /* @__PURE__ */ new Map();
|
|
15
72
|
for (let index = 0; index < argv.length; index += 1) {
|
package/index.d.ts
CHANGED
|
@@ -111,12 +111,17 @@ interface WaitForPubsubSubscriberOptions {
|
|
|
111
111
|
}
|
|
112
112
|
interface RelayButtonDriverOptions {
|
|
113
113
|
launcherName?: string | RegExp;
|
|
114
|
+
/** Accessible label of the instance-name field (@le-space/ui's Svelte form labels it, no placeholder). */
|
|
115
|
+
instanceNameLabel?: string;
|
|
114
116
|
instanceNamePlaceholder?: string;
|
|
117
|
+
/** Accessible label of the SSH-key field (@le-space/ui's Svelte form labels it, no placeholder). */
|
|
118
|
+
sshPublicKeyLabel?: string;
|
|
115
119
|
sshPublicKeyPlaceholder?: string;
|
|
116
120
|
connectWalletName?: string;
|
|
117
121
|
deployButtonName?: string;
|
|
118
122
|
deleteButtonName?: string;
|
|
119
123
|
refreshButtonName?: string;
|
|
124
|
+
advancedToggleName?: string;
|
|
120
125
|
}
|
|
121
126
|
interface ProvisionRelayOptions {
|
|
122
127
|
accountAddress: string;
|
|
@@ -203,6 +208,42 @@ declare function selectExpiredAlephPlaywrightRunners(options: {
|
|
|
203
208
|
ttlMs?: number;
|
|
204
209
|
}): AlephRunnerJanitorSelection;
|
|
205
210
|
declare function waitForPubsubSubscriber(page: Page, options: WaitForPubsubSubscriberOptions): Promise<string[]>;
|
|
211
|
+
/**
|
|
212
|
+
* Default console filter for libp2p connectivity diagnostics — keeps the
|
|
213
|
+
* relay/dial/peer/webrtc/circuit/reservation/connect lines plus errors/warnings
|
|
214
|
+
* and drops the rest of the (very chatty) browser console.
|
|
215
|
+
*/
|
|
216
|
+
declare const LIBP2P_DIAGNOSTIC_CONSOLE_FILTER: RegExp;
|
|
217
|
+
interface ProgressLogger {
|
|
218
|
+
/** Log a free-form progress line: `[label HH:MM:SS.mmm] message`. */
|
|
219
|
+
progress(message: string): void;
|
|
220
|
+
/** Log a stage marker: `[label HH:MM:SS.mmm] stage: name (detail)`. */
|
|
221
|
+
stage(name: string, detail?: string): void;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* A tiny timestamped stage/progress logger so consumers stream what a remote
|
|
225
|
+
* E2E is doing instead of going silent for minutes. Mirrors simple-todo's
|
|
226
|
+
* `[remote-e2e] stage: …` format so both consumers read the same way.
|
|
227
|
+
*/
|
|
228
|
+
declare function createProgressLogger(options?: {
|
|
229
|
+
label?: string;
|
|
230
|
+
log?: (line: string) => void;
|
|
231
|
+
}): ProgressLogger;
|
|
232
|
+
interface ForwardBrowserConsoleOptions {
|
|
233
|
+
/** Prefix identifying the browser, e.g. `local` or `aleph-remote`. */
|
|
234
|
+
label: string;
|
|
235
|
+
log?: (line: string) => void;
|
|
236
|
+
/** Only forward matching lines. Defaults to {@link LIBP2P_DIAGNOSTIC_CONSOLE_FILTER}; pass `null` to forward everything. */
|
|
237
|
+
filter?: RegExp | ((text: string) => boolean) | null;
|
|
238
|
+
/** Truncate each forwarded line to this length (default 300). */
|
|
239
|
+
maxLength?: number;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Forward a page's browser console + page errors into the test log so libp2p
|
|
243
|
+
* connection/discovery activity is visible in CI. Filtered by default to the
|
|
244
|
+
* connectivity-relevant lines.
|
|
245
|
+
*/
|
|
246
|
+
declare function forwardBrowserConsole(page: Page, options: ForwardBrowserConsoleOptions): void;
|
|
206
247
|
declare function installEip1193WalletMock(context: BrowserContext, account: RelayWalletAccount): Promise<void>;
|
|
207
248
|
declare class RelayButtonDriver {
|
|
208
249
|
readonly page: Page;
|
|
@@ -210,6 +251,15 @@ declare class RelayButtonDriver {
|
|
|
210
251
|
constructor(page: Page, options?: RelayButtonDriverOptions);
|
|
211
252
|
deployButton(): Locator;
|
|
212
253
|
instance(instanceName: string): Locator;
|
|
254
|
+
/**
|
|
255
|
+
* The instance-name field, matched by accessible label OR placeholder so the
|
|
256
|
+
* driver works for both @le-space/ui builds: the Svelte form labels the field
|
|
257
|
+
* (`<label><span>Instance Name</span><input>`) with no placeholder, while the
|
|
258
|
+
* React build renders a `placeholder="Instance name"`.
|
|
259
|
+
*/
|
|
260
|
+
instanceNameField(): Locator;
|
|
261
|
+
/** The SSH-key field, matched by accessible label OR placeholder (see {@link instanceNameField}). */
|
|
262
|
+
sshPublicKeyField(): Locator;
|
|
213
263
|
prepare(options: {
|
|
214
264
|
instanceName: string;
|
|
215
265
|
sshPublicKey: string;
|
|
@@ -266,4 +316,4 @@ declare function createRelayTest(options: CreateRelayTestOptions): _playwright_t
|
|
|
266
316
|
relayLifecycle: RelayLifecycleFixture;
|
|
267
317
|
}, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
|
|
268
318
|
|
|
269
|
-
export { type AlephChromiumConnector, type AlephCostEvidence, type AlephCreditBalanceSnapshot, type AlephPricingSnapshot, type AlephRemoteBrowserOptions, type AlephRunnerInstanceCandidate, type AlephRunnerJanitorSelection, type CleanupRelayHooks, type CleanupRelayOptions, type CleanupRelayResult, type CreateRelayTestOptions, DEFAULT_ALEPH_SCHEDULER_URL, PLAYWRIGHT_RUNNER_VERSION, type ProvisionRelayOptions, type ProvisionedRelay, type RelayAddressPolicy, RelayButtonDriver, type RelayButtonDriverOptions, type RelayEvidence, type RelayEvidenceStep, type RelayLifecycleFixture, type RelayWalletAccount, SUPPORTED_ALEPH_API_HOSTS, type WaitForPubsubSubscriberOptions, appendRelayGithubSummary, buildAlephCostEvidence, cleanupRelay, connectAlephChromium, createRelayEvidence, createRelayTest, findAlephInstanceHash, formatAlephCostGithubSummary, formatRelayGithubSummary, installEip1193WalletMock, provisionRelay, resolveAlephApiHosts, selectBrowserRelayAddresses, selectExpiredAlephPlaywrightRunners, updateRelayEvidenceStep, waitForAlephInstanceDeletion, waitForAlephMessageForgotten, waitForBootstrapRegistration, waitForDeployableManifest, waitForPubsubSubscriber, writeRelayEvidence };
|
|
319
|
+
export { type AlephChromiumConnector, type AlephCostEvidence, type AlephCreditBalanceSnapshot, type AlephPricingSnapshot, type AlephRemoteBrowserOptions, type AlephRunnerInstanceCandidate, type AlephRunnerJanitorSelection, type CleanupRelayHooks, type CleanupRelayOptions, type CleanupRelayResult, type CreateRelayTestOptions, DEFAULT_ALEPH_SCHEDULER_URL, type ForwardBrowserConsoleOptions, LIBP2P_DIAGNOSTIC_CONSOLE_FILTER, PLAYWRIGHT_RUNNER_VERSION, type ProgressLogger, type ProvisionRelayOptions, type ProvisionedRelay, type RelayAddressPolicy, RelayButtonDriver, type RelayButtonDriverOptions, type RelayEvidence, type RelayEvidenceStep, type RelayLifecycleFixture, type RelayWalletAccount, SUPPORTED_ALEPH_API_HOSTS, type WaitForPubsubSubscriberOptions, appendRelayGithubSummary, buildAlephCostEvidence, cleanupRelay, connectAlephChromium, createProgressLogger, createRelayEvidence, createRelayTest, findAlephInstanceHash, formatAlephCostGithubSummary, formatRelayGithubSummary, forwardBrowserConsole, installEip1193WalletMock, provisionRelay, resolveAlephApiHosts, selectBrowserRelayAddresses, selectExpiredAlephPlaywrightRunners, updateRelayEvidenceStep, waitForAlephInstanceDeletion, waitForAlephMessageForgotten, waitForBootstrapRegistration, waitForDeployableManifest, waitForPubsubSubscriber, writeRelayEvidence };
|
package/index.js
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_ALEPH_SCHEDULER_URL,
|
|
3
|
-
SUPPORTED_ALEPH_API_HOSTS,
|
|
4
|
-
delay,
|
|
5
|
-
resolveAlephApiHosts,
|
|
6
|
-
waitForAlephInstanceDeletion
|
|
7
|
-
} from "./chunk-YAODEYKV.js";
|
|
8
|
-
|
|
9
1
|
// src/index.ts
|
|
10
2
|
import { createHash } from "crypto";
|
|
11
3
|
import { appendFile, mkdir, writeFile } from "fs/promises";
|
|
@@ -13,6 +5,67 @@ import { dirname } from "path";
|
|
|
13
5
|
import { fetchAlephBootstrapPosts } from "@le-space/aleph-bootstrap";
|
|
14
6
|
import { eraseInstanceOnCrn, forgetAlephMessages } from "@le-space/core";
|
|
15
7
|
import { test as playwrightTest } from "@playwright/test";
|
|
8
|
+
|
|
9
|
+
// src/aleph-instance.ts
|
|
10
|
+
var SUPPORTED_ALEPH_API_HOSTS = ["https://api2.aleph.im", "https://api.aleph.im"];
|
|
11
|
+
var DEFAULT_ALEPH_SCHEDULER_URL = "https://scheduler.api.aleph.cloud";
|
|
12
|
+
var API3_HOST_PATTERN = /(^|\.)api3\.aleph\.im$/iu;
|
|
13
|
+
function delay(ms) {
|
|
14
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15
|
+
}
|
|
16
|
+
function normalizeApiOrigin(value) {
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(value);
|
|
19
|
+
if (API3_HOST_PATTERN.test(url.hostname)) return null;
|
|
20
|
+
return url.origin;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function resolveAlephApiHosts(candidates) {
|
|
26
|
+
const allowed = new Set((candidates ?? SUPPORTED_ALEPH_API_HOSTS).map(normalizeApiOrigin).filter((value) => value != null));
|
|
27
|
+
const selected = SUPPORTED_ALEPH_API_HOSTS.filter((host) => allowed.has(host));
|
|
28
|
+
return selected.length > 0 ? [...selected] : [...SUPPORTED_ALEPH_API_HOSTS];
|
|
29
|
+
}
|
|
30
|
+
async function waitForAlephInstanceDeletion(options) {
|
|
31
|
+
const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
32
|
+
if (!fetchImpl) throw new Error("A fetch implementation is required for cleanup verification");
|
|
33
|
+
const deadline = Date.now() + (options.timeoutMs ?? 5 * 6e4);
|
|
34
|
+
const hosts = resolveAlephApiHosts(options.apiHosts);
|
|
35
|
+
const schedulerUrl = new URL(`/api/v0/allocation/${options.instanceHash}`, options.schedulerUrl ?? DEFAULT_ALEPH_SCHEDULER_URL);
|
|
36
|
+
let lastSummary = "Deletion has not been observed yet.";
|
|
37
|
+
while (Date.now() < deadline) {
|
|
38
|
+
const observations = [];
|
|
39
|
+
let replicasForgotten = true;
|
|
40
|
+
for (const apiHost of hosts) {
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetchImpl(new URL(`/api/v0/messages/${options.instanceHash}`, apiHost), { cache: "no-cache" });
|
|
43
|
+
const payload = await response.json().catch(() => null);
|
|
44
|
+
const forgotten = payload?.status === "forgotten" || Boolean(payload?.forgotten_by?.length);
|
|
45
|
+
replicasForgotten &&= forgotten;
|
|
46
|
+
observations.push(`${apiHost}: ${forgotten ? "forgotten" : payload?.status ?? `HTTP ${response.status}`}`);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
replicasForgotten = false;
|
|
49
|
+
observations.push(`${apiHost}: ${error instanceof Error ? error.message : String(error)}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let unallocated = false;
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetchImpl(schedulerUrl, { cache: "no-cache" });
|
|
55
|
+
const payload = await response.json().catch(() => null);
|
|
56
|
+
unallocated = response.status === 404 || payload?.error === "VM is not allocated to any node";
|
|
57
|
+
observations.push(`scheduler: ${unallocated ? "unallocated" : `HTTP ${response.status}`}`);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
observations.push(`scheduler: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
|
+
}
|
|
61
|
+
lastSummary = observations.join("; ");
|
|
62
|
+
if (replicasForgotten && unallocated) return lastSummary;
|
|
63
|
+
await delay(options.pollIntervalMs ?? 2e3);
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`Aleph INSTANCE ${options.instanceHash} was not deleted within ${options.timeoutMs ?? 5 * 6e4}ms: ${lastSummary}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/index.ts
|
|
16
69
|
var PLAYWRIGHT_RUNNER_VERSION = "1.61.1";
|
|
17
70
|
function requireFiniteNumber(value, label) {
|
|
18
71
|
const number = Number(value);
|
|
@@ -153,6 +206,34 @@ async function waitForPubsubSubscriber(page, options) {
|
|
|
153
206
|
`PubSub subscriber ${options.peerId} was not stable on ${options.topic} within ${timeoutMs}ms; last subscribers: ${subscribers.join(", ") || "none"}`
|
|
154
207
|
);
|
|
155
208
|
}
|
|
209
|
+
var LIBP2P_DIAGNOSTIC_CONSOLE_FILTER = /error|warn|relay|dial|peer|webrtc|circuit|reservation|connect/i;
|
|
210
|
+
function createProgressLogger(options = {}) {
|
|
211
|
+
const label = options.label ?? "e2e";
|
|
212
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
213
|
+
const stamp = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
214
|
+
return {
|
|
215
|
+
progress(message) {
|
|
216
|
+
log(`[${label} ${stamp()}] ${message}`);
|
|
217
|
+
},
|
|
218
|
+
stage(name, detail) {
|
|
219
|
+
log(`[${label} ${stamp()}] stage: ${name}${detail ? ` (${detail})` : ""}`);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function forwardBrowserConsole(page, options) {
|
|
224
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
225
|
+
const maxLength = options.maxLength ?? 300;
|
|
226
|
+
const filter = options.filter === void 0 ? LIBP2P_DIAGNOSTIC_CONSOLE_FILTER : options.filter;
|
|
227
|
+
const matches = (text) => {
|
|
228
|
+
if (!filter) return true;
|
|
229
|
+
return filter instanceof RegExp ? filter.test(text) : filter(text);
|
|
230
|
+
};
|
|
231
|
+
page.on("console", (message) => {
|
|
232
|
+
const text = message.text();
|
|
233
|
+
if (matches(text)) log(`[${options.label} ${message.type()}] ${text}`.slice(0, maxLength));
|
|
234
|
+
});
|
|
235
|
+
page.on("pageerror", (error) => log(`[${options.label} pageerror] ${error.message}`.slice(0, maxLength)));
|
|
236
|
+
}
|
|
156
237
|
async function installEip1193WalletMock(context, account) {
|
|
157
238
|
await context.exposeBinding("__relayE2eWalletRequest", async (_source, request) => {
|
|
158
239
|
const { method, params = [] } = request;
|
|
@@ -201,12 +282,15 @@ var RelayButtonDriver = class {
|
|
|
201
282
|
this.page = page;
|
|
202
283
|
this.options = {
|
|
203
284
|
launcherName: options.launcherName ?? "Relay Button",
|
|
285
|
+
instanceNameLabel: options.instanceNameLabel ?? "Instance Name",
|
|
204
286
|
instanceNamePlaceholder: options.instanceNamePlaceholder ?? "Instance name",
|
|
287
|
+
sshPublicKeyLabel: options.sshPublicKeyLabel ?? "SSH Public Key",
|
|
205
288
|
sshPublicKeyPlaceholder: options.sshPublicKeyPlaceholder ?? "SSH public key",
|
|
206
289
|
connectWalletName: options.connectWalletName ?? "Connect MetaMask",
|
|
207
290
|
deployButtonName: options.deployButtonName ?? "Deploy Relay",
|
|
208
291
|
deleteButtonName: options.deleteButtonName ?? "Delete",
|
|
209
|
-
refreshButtonName: options.refreshButtonName ?? "Refresh"
|
|
292
|
+
refreshButtonName: options.refreshButtonName ?? "Refresh",
|
|
293
|
+
advancedToggleName: options.advancedToggleName ?? "Advanced"
|
|
210
294
|
};
|
|
211
295
|
}
|
|
212
296
|
deployButton() {
|
|
@@ -217,15 +301,28 @@ var RelayButtonDriver = class {
|
|
|
217
301
|
instance(instanceName) {
|
|
218
302
|
return this.page.locator("details").filter({ hasText: instanceName }).first();
|
|
219
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* The instance-name field, matched by accessible label OR placeholder so the
|
|
306
|
+
* driver works for both @le-space/ui builds: the Svelte form labels the field
|
|
307
|
+
* (`<label><span>Instance Name</span><input>`) with no placeholder, while the
|
|
308
|
+
* React build renders a `placeholder="Instance name"`.
|
|
309
|
+
*/
|
|
310
|
+
instanceNameField() {
|
|
311
|
+
return this.page.getByLabel(this.options.instanceNameLabel).or(this.page.getByPlaceholder(this.options.instanceNamePlaceholder)).first();
|
|
312
|
+
}
|
|
313
|
+
/** The SSH-key field, matched by accessible label OR placeholder (see {@link instanceNameField}). */
|
|
314
|
+
sshPublicKeyField() {
|
|
315
|
+
return this.page.getByLabel(this.options.sshPublicKeyLabel).or(this.page.getByPlaceholder(this.options.sshPublicKeyPlaceholder)).first();
|
|
316
|
+
}
|
|
220
317
|
async prepare(options) {
|
|
221
318
|
const launcher = this.page.getByRole("button", {
|
|
222
319
|
name: this.options.launcherName
|
|
223
320
|
});
|
|
224
321
|
await launcher.waitFor({ state: "visible", timeout: 6e4 });
|
|
225
322
|
await launcher.click();
|
|
226
|
-
await this.
|
|
227
|
-
await this.page.getByText(
|
|
228
|
-
await this.
|
|
323
|
+
await this.instanceNameField().fill(options.instanceName);
|
|
324
|
+
await this.page.getByText(this.options.advancedToggleName, { exact: true }).click();
|
|
325
|
+
await this.sshPublicKeyField().fill(options.sshPublicKey);
|
|
229
326
|
await this.page.getByRole("button", {
|
|
230
327
|
name: this.options.connectWalletName,
|
|
231
328
|
exact: true
|
|
@@ -723,6 +820,7 @@ function createRelayTest(options) {
|
|
|
723
820
|
}
|
|
724
821
|
export {
|
|
725
822
|
DEFAULT_ALEPH_SCHEDULER_URL,
|
|
823
|
+
LIBP2P_DIAGNOSTIC_CONSOLE_FILTER,
|
|
726
824
|
PLAYWRIGHT_RUNNER_VERSION,
|
|
727
825
|
RelayButtonDriver,
|
|
728
826
|
SUPPORTED_ALEPH_API_HOSTS,
|
|
@@ -730,11 +828,13 @@ export {
|
|
|
730
828
|
buildAlephCostEvidence,
|
|
731
829
|
cleanupRelay,
|
|
732
830
|
connectAlephChromium,
|
|
831
|
+
createProgressLogger,
|
|
733
832
|
createRelayEvidence,
|
|
734
833
|
createRelayTest,
|
|
735
834
|
findAlephInstanceHash,
|
|
736
835
|
formatAlephCostGithubSummary,
|
|
737
836
|
formatRelayGithubSummary,
|
|
837
|
+
forwardBrowserConsole,
|
|
738
838
|
installEip1193WalletMock,
|
|
739
839
|
provisionRelay,
|
|
740
840
|
resolveAlephApiHosts,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@le-space/playwright",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.36",
|
|
4
4
|
"description": "Reusable Playwright fixtures and Relay Button lifecycle helpers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"url": "https://github.com/NiKrause/relay-button/issues"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@le-space/aleph-bootstrap": "0.6.
|
|
31
|
-
"@le-space/core": "0.6.
|
|
30
|
+
"@le-space/aleph-bootstrap": "0.6.36",
|
|
31
|
+
"@le-space/core": "0.6.36"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@playwright/test": ">=1.61.1 <1.62.0"
|
package/chunk-YAODEYKV.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// src/aleph-instance.ts
|
|
2
|
-
var SUPPORTED_ALEPH_API_HOSTS = ["https://api2.aleph.im", "https://api.aleph.im"];
|
|
3
|
-
var DEFAULT_ALEPH_SCHEDULER_URL = "https://scheduler.api.aleph.cloud";
|
|
4
|
-
var API3_HOST_PATTERN = /(^|\.)api3\.aleph\.im$/iu;
|
|
5
|
-
function delay(ms) {
|
|
6
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
-
}
|
|
8
|
-
function normalizeApiOrigin(value) {
|
|
9
|
-
try {
|
|
10
|
-
const url = new URL(value);
|
|
11
|
-
if (API3_HOST_PATTERN.test(url.hostname)) return null;
|
|
12
|
-
return url.origin;
|
|
13
|
-
} catch {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
function resolveAlephApiHosts(candidates) {
|
|
18
|
-
const allowed = new Set((candidates ?? SUPPORTED_ALEPH_API_HOSTS).map(normalizeApiOrigin).filter((value) => value != null));
|
|
19
|
-
const selected = SUPPORTED_ALEPH_API_HOSTS.filter((host) => allowed.has(host));
|
|
20
|
-
return selected.length > 0 ? [...selected] : [...SUPPORTED_ALEPH_API_HOSTS];
|
|
21
|
-
}
|
|
22
|
-
async function waitForAlephInstanceDeletion(options) {
|
|
23
|
-
const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
24
|
-
if (!fetchImpl) throw new Error("A fetch implementation is required for cleanup verification");
|
|
25
|
-
const deadline = Date.now() + (options.timeoutMs ?? 5 * 6e4);
|
|
26
|
-
const hosts = resolveAlephApiHosts(options.apiHosts);
|
|
27
|
-
const schedulerUrl = new URL(`/api/v0/allocation/${options.instanceHash}`, options.schedulerUrl ?? DEFAULT_ALEPH_SCHEDULER_URL);
|
|
28
|
-
let lastSummary = "Deletion has not been observed yet.";
|
|
29
|
-
while (Date.now() < deadline) {
|
|
30
|
-
const observations = [];
|
|
31
|
-
let replicasForgotten = true;
|
|
32
|
-
for (const apiHost of hosts) {
|
|
33
|
-
try {
|
|
34
|
-
const response = await fetchImpl(new URL(`/api/v0/messages/${options.instanceHash}`, apiHost), { cache: "no-cache" });
|
|
35
|
-
const payload = await response.json().catch(() => null);
|
|
36
|
-
const forgotten = payload?.status === "forgotten" || Boolean(payload?.forgotten_by?.length);
|
|
37
|
-
replicasForgotten &&= forgotten;
|
|
38
|
-
observations.push(`${apiHost}: ${forgotten ? "forgotten" : payload?.status ?? `HTTP ${response.status}`}`);
|
|
39
|
-
} catch (error) {
|
|
40
|
-
replicasForgotten = false;
|
|
41
|
-
observations.push(`${apiHost}: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
let unallocated = false;
|
|
45
|
-
try {
|
|
46
|
-
const response = await fetchImpl(schedulerUrl, { cache: "no-cache" });
|
|
47
|
-
const payload = await response.json().catch(() => null);
|
|
48
|
-
unallocated = response.status === 404 || payload?.error === "VM is not allocated to any node";
|
|
49
|
-
observations.push(`scheduler: ${unallocated ? "unallocated" : `HTTP ${response.status}`}`);
|
|
50
|
-
} catch (error) {
|
|
51
|
-
observations.push(`scheduler: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
-
}
|
|
53
|
-
lastSummary = observations.join("; ");
|
|
54
|
-
if (replicasForgotten && unallocated) return lastSummary;
|
|
55
|
-
await delay(options.pollIntervalMs ?? 2e3);
|
|
56
|
-
}
|
|
57
|
-
throw new Error(`Aleph INSTANCE ${options.instanceHash} was not deleted within ${options.timeoutMs ?? 5 * 6e4}ms: ${lastSummary}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export {
|
|
61
|
-
SUPPORTED_ALEPH_API_HOSTS,
|
|
62
|
-
DEFAULT_ALEPH_SCHEDULER_URL,
|
|
63
|
-
delay,
|
|
64
|
-
resolveAlephApiHosts,
|
|
65
|
-
waitForAlephInstanceDeletion
|
|
66
|
-
};
|