170 lines
5.7 KiB
JavaScript
170 lines
5.7 KiB
JavaScript
/**
|
|
* Tests for tab lifecycle contract hardening (JO-2452, JO-2456).
|
|
*
|
|
* Covers:
|
|
* - Navigate returns stale-tab error (no auto-create)
|
|
* - Session lastAccess refreshed on tab ops (prevents timeout of active sessions)
|
|
* - Popup/new-window pages become managed tabs (not orphaned)
|
|
* - scroll and links are serialized via withTabLock
|
|
*/
|
|
|
|
import { startServer, stopServer, getServerUrl } from '../helpers/startServer.js';
|
|
import { startTestSite, stopTestSite, getTestSiteUrl } from '../helpers/testSite.js';
|
|
import { createClient } from '../helpers/client.js';
|
|
|
|
describe('Tab Lifecycle Contract', () => {
|
|
let serverUrl;
|
|
let testSiteUrl;
|
|
|
|
beforeAll(async () => {
|
|
await startServer(0, { SESSION_TIMEOUT_MS: '30000' });
|
|
serverUrl = getServerUrl();
|
|
await startTestSite();
|
|
testSiteUrl = getTestSiteUrl();
|
|
}, 120000);
|
|
|
|
afterAll(async () => {
|
|
await stopTestSite();
|
|
await stopServer();
|
|
}, 30000);
|
|
|
|
describe('Stale tab errors (JO-2452)', () => {
|
|
test('navigate returns 404 for unknown tabId', async () => {
|
|
const client = createClient(serverUrl);
|
|
try {
|
|
await client.createTab(`${testSiteUrl}/pageA`);
|
|
try {
|
|
await client.navigate('nonexistent-tab-id', `${testSiteUrl}/pageB`);
|
|
fail('Should have thrown');
|
|
} catch (err) {
|
|
expect(err.status).toBe(404);
|
|
}
|
|
} finally {
|
|
await client.cleanup();
|
|
}
|
|
}, 60000);
|
|
|
|
test('snapshot returns 404 for unknown tabId', async () => {
|
|
const client = createClient(serverUrl);
|
|
try {
|
|
await client.createTab(`${testSiteUrl}/pageA`);
|
|
try {
|
|
await client.getSnapshot('nonexistent-tab-id');
|
|
fail('Should have thrown');
|
|
} catch (err) {
|
|
expect(err.status).toBe(404);
|
|
}
|
|
} finally {
|
|
await client.cleanup();
|
|
}
|
|
}, 60000);
|
|
|
|
test('click returns 404 for unknown tabId', async () => {
|
|
const client = createClient(serverUrl);
|
|
try {
|
|
await client.createTab(`${testSiteUrl}/pageA`);
|
|
try {
|
|
await client.click('nonexistent-tab-id', { ref: 'e1' });
|
|
fail('Should have thrown');
|
|
} catch (err) {
|
|
expect(err.status).toBe(404);
|
|
}
|
|
} finally {
|
|
await client.cleanup();
|
|
}
|
|
}, 60000);
|
|
|
|
test('scroll returns 404 for unknown tabId', async () => {
|
|
const client = createClient(serverUrl);
|
|
try {
|
|
await client.createTab(`${testSiteUrl}/pageA`);
|
|
try {
|
|
await client.scroll('nonexistent-tab-id', { direction: 'down', amount: 100 });
|
|
fail('Should have thrown');
|
|
} catch (err) {
|
|
expect(err.status).toBe(404);
|
|
}
|
|
} finally {
|
|
await client.cleanup();
|
|
}
|
|
}, 60000);
|
|
});
|
|
|
|
describe('Session lastAccess refresh (JO-2452)', () => {
|
|
test('tab operations refresh session lastAccess', async () => {
|
|
const client = createClient(serverUrl);
|
|
try {
|
|
const { tabId } = await client.createTab(`${testSiteUrl}/links`);
|
|
|
|
// Perform multiple tab operations — session should not expire
|
|
await new Promise(r => setTimeout(r, 2000));
|
|
await client.getSnapshot(tabId);
|
|
await new Promise(r => setTimeout(r, 2000));
|
|
await client.getLinks(tabId);
|
|
await new Promise(r => setTimeout(r, 2000));
|
|
await client.scroll(tabId, { direction: 'down', amount: 100 });
|
|
await new Promise(r => setTimeout(r, 2000));
|
|
|
|
// Session should still be alive after ops (each refreshed lastAccess)
|
|
const snapshot = await client.getSnapshot(tabId);
|
|
expect(snapshot.url).toContain('/links');
|
|
} finally {
|
|
await client.cleanup();
|
|
}
|
|
}, 60000);
|
|
});
|
|
|
|
describe('Popup/new-window policy (JO-2456)', () => {
|
|
test('target=_blank link creates a managed tab', async () => {
|
|
const client = createClient(serverUrl);
|
|
try {
|
|
const { tabId } = await client.createTab(`${testSiteUrl}/popup-source`);
|
|
|
|
// Click the target=_blank link
|
|
await client.click(tabId, { selector: '#blankLink' });
|
|
|
|
// Wait briefly for popup event to register
|
|
await new Promise(r => setTimeout(r, 1500));
|
|
|
|
// List tabs — should see more than 1 tab (the popup is now managed)
|
|
const res = await client.request('GET', `/tabs?userId=${client.userId}`);
|
|
expect(res.tabs.length).toBeGreaterThanOrEqual(2);
|
|
|
|
// Find the popup tab
|
|
const popupTab = res.tabs.find(t => t.url.includes('/popup-target'));
|
|
expect(popupTab).toBeDefined();
|
|
expect(popupTab.tabId).toBeDefined();
|
|
|
|
// The popup tab should be closeable via the API
|
|
await client.closeTab(popupTab.tabId);
|
|
const res2 = await client.request('GET', `/tabs?userId=${client.userId}`);
|
|
const stillThere = res2.tabs.find(t => t.tabId === popupTab.tabId);
|
|
expect(stillThere).toBeUndefined();
|
|
} finally {
|
|
await client.cleanup();
|
|
}
|
|
}, 60000);
|
|
|
|
test('window.open creates a managed tab', async () => {
|
|
const client = createClient(serverUrl);
|
|
try {
|
|
const { tabId } = await client.createTab(`${testSiteUrl}/popup-source`);
|
|
|
|
// Click the window.open button
|
|
await client.click(tabId, { selector: '#openBtn' });
|
|
|
|
// Wait briefly for popup event to register
|
|
await new Promise(r => setTimeout(r, 1500));
|
|
|
|
// List tabs — popup should be tracked
|
|
const res = await client.request('GET', `/tabs?userId=${client.userId}`);
|
|
const popupTab = res.tabs.find(t => t.url.includes('/popup-target'));
|
|
expect(popupTab).toBeDefined();
|
|
expect(popupTab.tabId).toBeDefined();
|
|
} finally {
|
|
await client.cleanup();
|
|
}
|
|
}, 60000);
|
|
});
|
|
});
|