feat(pairing): QR offer + app scanner
This commit is contained in:
@@ -36,6 +36,7 @@ test.beforeEach(async ({ page }) => {
|
||||
});
|
||||
|
||||
const nowIso = new Date().toISOString();
|
||||
const seedNonce = Math.random().toString(36).slice(2);
|
||||
const testDaemon = {
|
||||
id: 'e2e-test-daemon',
|
||||
label: 'localhost',
|
||||
@@ -58,24 +59,28 @@ test.beforeEach(async ({ page }) => {
|
||||
};
|
||||
|
||||
await page.addInitScript(
|
||||
({ daemon, preferences }) => {
|
||||
({ daemon, preferences, seedNonce }) => {
|
||||
// `addInitScript` runs on every navigation (including reloads). Some tests intentionally
|
||||
// override storage and reload; they can opt out of seeding for the *next* navigation by
|
||||
// setting this flag before the reload.
|
||||
const disableOnceKey = '@paseo:e2e-disable-default-seed-once';
|
||||
if (localStorage.getItem(disableOnceKey)) {
|
||||
const disableValue = localStorage.getItem(disableOnceKey);
|
||||
if (disableValue) {
|
||||
localStorage.removeItem(disableOnceKey);
|
||||
return;
|
||||
if (disableValue === seedNonce) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem('@paseo:e2e', '1');
|
||||
localStorage.setItem('@paseo:e2e-seed-nonce', seedNonce);
|
||||
|
||||
// Hard-reset anything that could point to a developer's real daemon.
|
||||
localStorage.setItem('@paseo:daemon-registry', JSON.stringify([daemon]));
|
||||
localStorage.removeItem('@paseo:settings');
|
||||
localStorage.setItem('@paseo:create-agent-preferences', JSON.stringify(preferences));
|
||||
},
|
||||
{ daemon: testDaemon, preferences: createAgentPreferences }
|
||||
{ daemon: testDaemon, preferences: createAgentPreferences, seedNonce }
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -190,7 +190,8 @@ export const ensureHostSelected = async (page: Page) => {
|
||||
prefs.serverId = daemonId;
|
||||
localStorage.setItem('@paseo:create-agent-preferences', JSON.stringify(prefs));
|
||||
// Prevent the fixture's init-script from overwriting the corrected prefs on reload.
|
||||
localStorage.setItem('@paseo:e2e-disable-default-seed-once', '1');
|
||||
const nonce = localStorage.getItem('@paseo:e2e-seed-nonce') ?? '1';
|
||||
localStorage.setItem('@paseo:e2e-disable-default-seed-once', nonce);
|
||||
return { ok: true } as const;
|
||||
});
|
||||
|
||||
|
||||
@@ -20,14 +20,24 @@ test('host removal removes the host from UI and persists after reload', async ({
|
||||
updatedAt: nowIso,
|
||||
};
|
||||
|
||||
const seededTestDaemon = {
|
||||
id: 'e2e-test-daemon',
|
||||
label: 'localhost',
|
||||
endpoints: [`127.0.0.1:${daemonPort}`],
|
||||
relay: null,
|
||||
metadata: null,
|
||||
createdAt: nowIso,
|
||||
updatedAt: nowIso,
|
||||
};
|
||||
|
||||
const seedOnceKey = `@paseo:e2e-host-removal-seeded:${Math.random().toString(36).slice(2)}`;
|
||||
|
||||
// Add a second host once (fixtures seed the primary host on every navigation).
|
||||
await page.addInitScript(
|
||||
({ daemon }) => {
|
||||
const seedOnceKey = '@paseo:e2e-extra-daemon-seeded-once';
|
||||
({ daemon, seededTestDaemon, seedOnceKey }) => {
|
||||
if (localStorage.getItem(seedOnceKey)) {
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(seedOnceKey, '1');
|
||||
|
||||
const raw = localStorage.getItem('@paseo:daemon-registry');
|
||||
let parsed: any[] = [];
|
||||
@@ -38,10 +48,23 @@ test('host removal removes the host from UI and persists after reload', async ({
|
||||
parsed = [];
|
||||
}
|
||||
}
|
||||
const next = Array.isArray(parsed) ? [...parsed, daemon] : [daemon];
|
||||
const list = Array.isArray(parsed) ? parsed : [];
|
||||
localStorage.setItem(seedOnceKey, '1');
|
||||
|
||||
const next = [...list];
|
||||
const hasSeeded = next.some((entry: any) => entry && entry.id === seededTestDaemon.id);
|
||||
if (!hasSeeded) {
|
||||
next.push(seededTestDaemon);
|
||||
}
|
||||
|
||||
const alreadyPresent = next.some((entry: any) => entry && entry.id === daemon.id);
|
||||
if (!alreadyPresent) {
|
||||
next.push(daemon);
|
||||
}
|
||||
|
||||
localStorage.setItem('@paseo:daemon-registry', JSON.stringify(next));
|
||||
},
|
||||
{ daemon: extraDaemon }
|
||||
{ daemon: extraDaemon, seededTestDaemon, seedOnceKey }
|
||||
);
|
||||
|
||||
await page.goto('/settings');
|
||||
@@ -72,8 +95,10 @@ test('host removal removes the host from UI and persists after reload', async ({
|
||||
);
|
||||
|
||||
// Prevent the fixture from overwriting storage on reload; verify persistence.
|
||||
await page.evaluate(() => localStorage.setItem('@paseo:e2e-disable-default-seed-once', '1'));
|
||||
await page.evaluate(() => {
|
||||
const nonce = localStorage.getItem('@paseo:e2e-seed-nonce') ?? '1';
|
||||
localStorage.setItem('@paseo:e2e-disable-default-seed-once', nonce);
|
||||
});
|
||||
await page.reload();
|
||||
await expect(page.getByText(extraEndpoint, { exact: true })).toHaveCount(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ test('new agent respects serverId in the URL', async ({ page }) => {
|
||||
await page.goto('/settings');
|
||||
await page.evaluate(
|
||||
({ daemon, preferences }) => {
|
||||
localStorage.setItem('@paseo:e2e-disable-default-seed-once', '1');
|
||||
const nonce = localStorage.getItem('@paseo:e2e-seed-nonce') ?? '1';
|
||||
localStorage.setItem('@paseo:e2e-disable-default-seed-once', nonce);
|
||||
localStorage.setItem('@paseo:daemon-registry', JSON.stringify([daemon]));
|
||||
localStorage.setItem('@paseo:create-agent-preferences', JSON.stringify(preferences));
|
||||
localStorage.removeItem('@paseo:settings');
|
||||
|
||||
@@ -18,7 +18,8 @@ test('pairing flow accepts #offer=ConnectionOfferV1 and stores sessionId + endpo
|
||||
// Override the default fixture seeding for this test.
|
||||
await page.goto('/settings');
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('@paseo:e2e-disable-default-seed-once', '1');
|
||||
const nonce = localStorage.getItem('@paseo:e2e-seed-nonce') ?? '1';
|
||||
localStorage.setItem('@paseo:e2e-disable-default-seed-once', nonce);
|
||||
localStorage.setItem('@paseo:daemon-registry', JSON.stringify([]));
|
||||
localStorage.removeItem('@paseo:settings');
|
||||
});
|
||||
@@ -35,6 +36,7 @@ test('pairing flow accepts #offer=ConnectionOfferV1 and stores sessionId + endpo
|
||||
|
||||
await page.getByText('+ Add Host', { exact: true }).click();
|
||||
await page.getByText('Pair', { exact: true }).click();
|
||||
await page.getByText('Paste link', { exact: true }).click();
|
||||
|
||||
const input = page.getByPlaceholder('https://app.paseo.sh/#offer=...');
|
||||
await expect(input).toBeVisible();
|
||||
|
||||
@@ -23,7 +23,8 @@ test('connects via relay when direct endpoints fail', async ({ page }) => {
|
||||
// Override the default fixture seeding for this test.
|
||||
await page.goto('/settings');
|
||||
await page.evaluate((daemon) => {
|
||||
localStorage.setItem('@paseo:e2e-disable-default-seed-once', '1');
|
||||
const nonce = localStorage.getItem('@paseo:e2e-seed-nonce') ?? '1';
|
||||
localStorage.setItem('@paseo:e2e-disable-default-seed-once', nonce);
|
||||
localStorage.setItem('@paseo:daemon-registry', JSON.stringify([daemon]));
|
||||
localStorage.removeItem('@paseo:settings');
|
||||
}, host);
|
||||
|
||||
Reference in New Issue
Block a user