test/contextual-search-browser.test.cjs
Rama main · réplica de lectura del repositorio original.
var test = require('node:test'), assert = require('node:assert/strict'), cp = require('node:child_process'), path = require('node:path'), playwright = require('playwright');
test('global Ask enters a fresh exact contextual Clay session without inline chat or duplicate delivery', async function (t) {
var child = cp.fork(path.join(__dirname, 'fixtures/contextual-clay-server.cjs'), [], { env: Object.assign({}, process.env, { CLAY_CONTEXT_ACCESS_FIXTURE: '1' }), stdio: ['ignore', 'pipe', 'pipe', 'ipc'] });
t.after(function () { child.kill('SIGTERM'); }); var output = '';
child.stdout.on('data', function (value) { output += value; }); child.stderr.on('data', function (value) { output += value; });
var info = await new Promise(function (resolve, reject) { var timer = setTimeout(function () { reject(new Error(output)); }, 15000); child.once('message', function (value) { clearTimeout(timer); resolve(value); }); });
var browser = await playwright.chromium.launch(); t.after(async function () { await browser.close(); });
var page = await browser.newPage({ viewport: { width: 1280, height: 800 }, reducedMotion: 'reduce' }); page.setDefaultTimeout(10000);
var errors = []; page.on('pageerror', function (error) { errors.push(error.message); });
var origin = 'http://127.0.0.1:' + info.port;
await page.goto(origin + '/fixture/login?to=alpha');
await page.waitForFunction(function () { return !document.querySelector('.icon-strip-brand').disabled; });
await page.evaluate(async function () { window.askStore = (await import('/p/alpha/modules/store.js')).store; window.askSocket = await import('/p/alpha/modules/ws-ref.js'); });
var projectSession = await page.evaluate(function () { return window.askStore.get('activeSessionId'); });
await page.locator('#input').fill('Project draft remains');
await page.locator('.icon-strip-brand').click();
var original = page.frameLocator('#contextual-clay iframe[data-canonical="true"]');
await original.locator('#messages').getByText('Isolated canonical integration conversation.', { exact: true }).waitFor();
await original.locator('#input-wrapper:not([inert])').waitFor();
await original.locator('#input').fill('Prior pinned Clay draft');
await page.request.get(origin + '/fixture/stream');
await original.locator('#messages').getByText('CONTEXTUAL_STREAM_CONTINUES', { exact: false }).waitFor();
await page.locator('[data-clay-close]').click();
async function ask(text, keyboard) {
await page.getByRole('button', { name: 'Search or ask Clay', exact: true }).click();
var input = page.locator('.cmd-palette-input'); await input.fill(text);
await page.locator('.is-ask-clay').waitFor();
if (keyboard) { await input.press('ArrowUp'); await input.press('Enter'); }
else await page.locator('.is-ask-clay').evaluate(function (node) { node.click(); node.click(); });
await page.locator('.cmd-palette:not(.project-switcher)').waitFor({ state: 'hidden' });
assert.equal(await page.locator('.search-clay-chat').count(), 0);
}
async function receipt() {
await page.waitForFunction(function () { var value = window.askStore.get('contextualSearchAsk'); return value && value.status === 'acknowledged'; });
return page.evaluate(function () { var value = window.askStore.get('contextualSearchAsk'); return { sessionId: value.sessionId, requestId: value.requestId, clientMessageId: value.clientMessageId }; });
}
function frame(id) { return page.frameLocator('#contextual-clay iframe[data-canonical="true"][data-session-id="' + id + '"]'); }
async function sessions() { return (await page.request.get(origin + '/fixture/contextual-asks')).json(); }
await ask('First fresh Ask from search', true); var first = await receipt();
assert.equal(new URL(page.url()).pathname, '/p/alpha/');
assert.equal(await page.evaluate(function () { return window.askStore.get('activeSessionId'); }), projectSession);
assert.equal(await page.locator('#input').inputValue(), 'Project draft remains');
await frame(first.sessionId).locator('#messages').getByText('First fresh Ask from search', { exact: true }).waitFor();
assert.equal((await sessions()).length, 1);
await page.locator('[data-mate-history] summary').click();
await page.getByRole('button', { name: 'Canonical integration source', exact: false }).click();
await page.waitForFunction(function () { var request = window.askStore.get('contextualConversationRequest'); return request && request.status === 'done'; });
await page.locator('[data-mate-history]:not([open])').waitFor();
assert.equal(await page.frameLocator('#contextual-clay iframe:visible').locator('#input').inputValue(), 'Prior pinned Clay draft');
await page.frameLocator('#contextual-clay iframe:visible').locator('#messages').getByText('CONTEXTUAL_STREAM_CONTINUES', { exact: false }).waitFor();
await page.locator('[data-mate-history] summary').click();
await page.getByRole('button', { name: 'First fresh Ask from search', exact: false }).click();
await page.waitForFunction(function () { var request = window.askStore.get('contextualConversationRequest'); return request && request.status === 'done'; });
await page.locator('[data-mate-history]:not([open])').waitFor();
await ask('Second fresh Ask from search', false); var second = await receipt();
assert.notEqual(first.sessionId, second.sessionId); assert.equal((await sessions()).length, 2);
for (var entry of await sessions()) assert.equal(entry.messages.filter(function (message) { return message.type === 'user_message'; }).length, 1);
var proposed = page.request.get(origin + '/fixture/ask-propose?session=' + second.sessionId);
var exactReview = frame(second.sessionId).locator('.ask-user-container:not(.answered)');
await exactReview.locator('.product-review-action').waitFor();
assert.match(await exactReview.locator('.product-review-action').textContent(), /Beta destination/);
await exactReview.getByText('Keep unchanged', { exact: true }).click(); await exactReview.locator('.ask-user-submit').click();
assert.equal((await (await proposed).json()).status, 'cancelled');
assert.equal(new URL(page.url()).pathname, '/p/alpha/');
var question = page.request.get(origin + '/fixture/ask-question?session=' + second.sessionId);
var review = frame(second.sessionId).locator('.ask-user-container:not(.answered)'); await review.waitFor();
await review.getByText('Keep unchanged', { exact: true }).click(); await review.locator('.ask-user-submit').click();
assert.equal((await (await question).json()).status, 'submitted');
await page.request.get(origin + '/fixture/ask-stream?session=' + second.sessionId);
await frame(second.sessionId).locator('#messages').getByText('NEW_ASK_STREAM_IN_PANE', { exact: false }).waitFor();
var currentFrame = page.frames().find(function (item) { return item.url().includes('&session=' + second.sessionId); });
await currentFrame.evaluate(async function () { window.askWire = []; var socket = (await import('./modules/ws-ref.js')).getWs(), send = socket.send.bind(socket); socket.send = function (data) { window.askWire.push(JSON.parse(data)); return send(data); }; });
await frame(second.sessionId).locator('#send-btn.stop').click();
assert.equal(await currentFrame.evaluate(function () { return window.askWire.filter(function (message) { return message.type === 'stop'; }).length; }), 1);
await page.locator('[data-clay-expand]').click(); await page.waitForURL('**/p/' + info.sourceSlug + '/');
await page.waitForFunction(function (id) { return window.askStore.get('activeSessionId') === id && window.askStore.get('contextualWorkspace').stage === 'workspace'; }, second.sessionId);
await page.locator('#messages').getByText('Second fresh Ask from search', { exact: true }).waitFor();
await page.locator('#contextual-workspace-back').click(); await page.waitForURL('**/p/alpha/');
await page.waitForFunction(function () { return !window.askStore.get('contextualWorkspace'); });
assert.equal(await page.locator('#input').inputValue(), 'Project draft remains');
// Both native background kinds retain their exact active session and draft.
for (var target of [{ slug: info.sourceSlug, sessionId: info.sourceSessionId }, info.ordinaryMates[0]]) {
await page.evaluate(async function (target) { (await import('/p/alpha/modules/project-session-navigation.js')).openProjectSession(target.slug, target.sessionId); }, target);
await page.waitForFunction(function (target) { return window.askStore.get('currentSlug') === target.slug && window.askStore.get('activeSessionId') === target.sessionId && !window.askStore.get('replayingHistory'); }, target);
await page.locator('#input').fill('Native background draft');
await ask('Ask over native ' + target.slug, false); var over = await receipt();
assert.equal(await page.evaluate(function () { return window.askStore.get('activeSessionId'); }), target.sessionId);
assert.equal(await page.locator('#input').inputValue(), 'Native background draft');
assert.equal(new URL(page.url()).pathname, '/p/' + target.slug + '/');
await page.locator('[data-clay-expand]').click();
await page.waitForFunction(function (id) { return window.askStore.get('activeSessionId') === id && window.askStore.get('contextualWorkspace').stage === 'workspace'; }, over.sessionId);
await page.locator('#contextual-workspace-back').click();
await page.waitForFunction(function (target) { return window.askStore.get('currentSlug') === target.slug && window.askStore.get('activeSessionId') === target.sessionId && !window.askStore.get('contextualWorkspace'); }, target);
assert.equal(await page.locator('#input').inputValue(), 'Native background draft');
}
await page.context().setOffline(true);
await page.waitForFunction(function () { return !window.askStore.get('connected'); });
await ask('Retain this offline question', false);
await page.locator('[data-contextual-ask-status]').filter({ hasText: 'Reconnect, then retry' }).waitFor();
await page.locator('[data-ask-stop]').click();
await page.context().setOffline(false);
await page.waitForFunction(function () { return window.askStore.get('connected'); });
assert.equal((await sessions()).length, 4, 'Stop/offline reconnect creates no session or message');
await page.locator('[data-ask-retry]').click(); var retried = await receipt();
assert.equal((await sessions()).length, 5);
var retriedFrame = page.frames().find(function (item) { return item.url().includes('&session=' + retried.sessionId); });
await retriedFrame.evaluate(async function () { (await import('./modules/ws-ref.js')).getWs().close(); });
await frame(retried.sessionId).locator('#input-wrapper:not([inert])').waitFor();
assert.equal((await sessions()).find(function (entry) { return entry.id === retried.sessionId; }).messages.filter(function (entry) { return entry.type === 'user_message'; }).length, 1);
// An uncertain create reply can be safely looked up, but Stop forbids submit.
await page.evaluate(function () { var socket = window.askSocket.getWs(), send = socket.send.bind(socket); window.releaseAsk = send; socket.send = function (data) { if (JSON.parse(data).type === 'home_clay_ask') window.heldAsk = data; else send(data); }; });
await ask('Stop while creating the new conversation', false);
await page.locator('[data-ask-stop]').click();
await page.evaluate(function () { window.releaseAsk(window.heldAsk); });
await page.waitForTimeout(150);
var stopped = (await sessions()).find(function (entry) { return entry.text === 'Stop while creating the new conversation'; });
assert.ok(stopped); assert.equal(stopped.messages.filter(function (entry) { return entry.type === 'user_message'; }).length, 0);
assert.equal(await page.evaluate(function () { return window.askStore.get('contextualSearchAsk').status; }), 'stopped');
await page.evaluate(function () { window.askSocket.getWs().send = window.releaseAsk; });
await page.locator('[data-ask-retry]').click(); var resumed = await receipt();
assert.equal(resumed.sessionId, stopped.id);
assert.equal((await sessions()).length, 6);
await ask('First fresh Ask from search', false); var repeatedText = await receipt();
assert.notEqual(repeatedText.sessionId, first.sessionId, 'A separate explicit Ask with identical text still creates a fresh session');
assert.equal((await sessions()).length, 7);
await page.evaluate(function () { var socket = window.askSocket.getWs(), send = socket.send.bind(socket); window.releaseAsk = send; socket.send = function (data) { if (JSON.parse(data).type === 'home_clay_ask') window.heldAsk = data; else send(data); }; });
await ask('Retain query when Clay disappears', false);
await page.evaluate(function () { window.savedAskMates = window.askStore.get('cachedMatesList'); window.askStore.set({ cachedMatesList: window.savedAskMates.filter(function (mate) { return mate.builtinKey !== 'clay'; }) }); });
await page.locator('[data-contextual-ask-status]').filter({ hasText: 'Clay is no longer available' }).waitFor();
await page.evaluate(function () { window.releaseAsk(window.heldAsk); });
await page.waitForTimeout(150);
var unavailable = (await sessions()).find(function (entry) { return entry.text === 'Retain query when Clay disappears'; });
assert.equal(unavailable.messages.filter(function (entry) { return entry.type === 'user_message'; }).length, 0);
assert.equal(await page.evaluate(function () { return window.askStore.get('contextualSearchAsk').text; }), 'Retain query when Clay disappears');
await page.evaluate(function () { window.askSocket.getWs().send = window.releaseAsk; window.askStore.set({ cachedMatesList: window.savedAskMates }); });
await page.locator('[data-ask-retry]').click(); await receipt();
assert.equal((await sessions()).length, 8);
await page.evaluate(function () {
var socket = window.askSocket.getWs(), receive = socket.onmessage;
socket.onmessage = function (event) { if (JSON.parse(event.data).type === 'home_clay_contextual_session') { window.heldAskReply = event; window.releaseAskReply = function () { receive.call(socket, event); }; } else receive.call(socket, event); };
});
await ask('Retain query after background navigation', false);
await page.waitForFunction(function () { return !!window.heldAskReply; });
await page.getByRole('link', { name: 'BI', exact: true }).click(); await page.waitForURL('**/p/beta/');
await page.waitForFunction(function () { return window.askStore.get('connected'); });
await page.evaluate(function () { window.releaseAskReply(); });
assert.notEqual(await page.evaluate(function () { return window.askStore.get('contextualSearchAsk').status; }), 'acknowledged');
assert.equal((await sessions()).find(function (entry) { return entry.text === 'Retain query after background navigation'; }).messages.filter(function (entry) { return entry.type === 'user_message'; }).length, 0);
await page.getByRole('button', { name: 'Search or ask Clay', exact: true }).click();
await page.locator('.cmd-palette-input').fill('');
await page.locator('.is-retry-clay-ask').click(); await receipt();
assert.equal((await sessions()).length, 9);
await page.setViewportSize({ width: 390, height: 844 });
await page.screenshot({ path: '/tmp/contextual-search-fresh-narrow.png', animations: 'disabled' });
assert.deepEqual(errors, []);
t.diagnostic('Central keyboard/Ask action -> fresh exact pane; duplicate activation once; two asks distinct; previous draft/stream accessible; actual review/stream/Stop confined to pane; native expansion/Back exact; project and both native backgrounds preserved; offline Stop/no replay, reconnect once and uncertain creation explicit resume reuse same session.');
});