Skip to content

Commit e48b871

Browse files
committed
Merge remote-tracking branch 'origin/feat/workspace-team' into wcl/updater-popup-restyle
2 parents 6c16990 + 4a66531 commit e48b871

4 files changed

Lines changed: 90 additions & 4 deletions

File tree

e2e/lib/playwright/visual.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@ const VISUAL_PROJECTS = [
136136

137137
type VisualProject = (typeof VISUAL_PROJECTS)[number];
138138

139+
/** The single conversation every workspace capture opens into. */
140+
const VISUAL_CONVERSATION = {
141+
id: 'visual-conversation-launchpad',
142+
projectId: 'visual-project-launchpad',
143+
title: null,
144+
messageCount: 0,
145+
createdAt: 1_700_000_000_000,
146+
updatedAt: 1_700_000_050_000,
147+
} as const;
148+
139149
const VISUAL_PROJECT_FILE_HTML =
140150
'<!doctype html><html><body><main><h1>Visual CSS Smoke</h1><p>Workspace preview remains framed.</p></main></body></html>';
141151

@@ -353,6 +363,42 @@ export async function configureVisualPage(page: Page, options: VisualPageOptions
353363
await fulfillGet(route, { projects });
354364
});
355365

366+
// The conversation boundary. `ProjectView` renders `ChatPane` — and therefore
367+
// the composer every workspace capture waits for — only once a conversation
368+
// resolves (`activeConversationId || conversationLoadError`), and both
369+
// `listConversations` and `createConversation` swallow a non-ok response
370+
// (`[]` / `null`) rather than surfacing an error. So while the catch-all above
371+
// answered `/conversations` with 404, the project opened with no conversation
372+
// AND no load error, ChatPane never mounted, and every capture that navigates
373+
// into the workspace died on `chat-composer` not existing. The catch-all's own
374+
// contract is that "every other daemon-owned request terminates at a
375+
// deterministic browser-side boundary" — this is that boundary for
376+
// conversations, which the catch-all closed without supplying.
377+
await page.route('**/api/projects/*/conversations', async (route) => {
378+
const method = route.request().method();
379+
if (method === 'GET') {
380+
await route.fulfill({ json: { conversations: [VISUAL_CONVERSATION] } });
381+
return;
382+
}
383+
if (method === 'POST') {
384+
await route.fulfill({ json: { conversation: VISUAL_CONVERSATION } });
385+
return;
386+
}
387+
await route.fallback();
388+
});
389+
390+
await page.route('**/api/projects/*/conversations/*', async (route) => {
391+
if (route.request().method() !== 'GET') {
392+
await route.fulfill({ json: { conversation: VISUAL_CONVERSATION } });
393+
return;
394+
}
395+
await fulfillGet(route, { conversation: VISUAL_CONVERSATION });
396+
});
397+
398+
await page.route('**/api/projects/*/conversations/*/messages', async (route) => {
399+
await fulfillGet(route, { messages: [] });
400+
});
401+
356402
await page.route('**/api/projects/*/files', async (route) => {
357403
if (route.request().method() !== 'GET') {
358404
await route.fallback();

e2e/ui/amr-run-failure-recovery.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,18 @@ async function openExecutionSettingsDialog(page: Page) {
5555
return settings;
5656
}
5757

58-
test.describe.configure({ mode: 'serial', timeout: T.xlong });
5958
// Timeout-only configure: each test stubs its own catalogs/agents/status
6059
// routes and creates its own project, so order independence holds and the
6160
// file stays splittable across CI shards (a serial group cannot be split).
61+
//
62+
// This must stay a SINGLE call. `test.describe.configure` only overwrites the
63+
// keys it is given, so a later `configure({ timeout })` cannot undo an earlier
64+
// `configure({ mode: 'serial' })` — a second call reading as "timeout-only"
65+
// left the whole file serial, where one failure skipped the eight cases behind
66+
// it and reported them as "did not run" rather than as real results.
67+
// `mode: 'serial'` is also forbidden outright by e2e/AGENTS.md's UI test
68+
// stability rules (a serial group cannot be split across the sharded full pool
69+
// and floors its wall time).
6270
test.describe.configure({ timeout: T.xlong });
6371

6472
async function stubCatalogsEmpty(page: Page) {

e2e/ui/visual-settings.test.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { Locator } from '@playwright/test';
2+
13
import type { WorkspaceCollabContext } from '@open-design/contracts';
24
import { expect, test } from '@/playwright/suite';
35
import { T } from '@/timeouts';
@@ -16,6 +18,27 @@ import {
1618

1719
test.describe.configure({ timeout: T.xlong });
1820

21+
/**
22+
* The BYOK half of Settings' Execution-mode switch.
23+
*
24+
* #5971 ("Polish Home and Settings terminology") renamed this tab's title from
25+
* `BYOK` to `API providers` — on `main` `settings.modeApiMeta` is still
26+
* `'BYOK'`, on this branch it is `'API providers'` — so the old
27+
* `getByRole('tab', { name: 'BYOK' })` matched nothing and every BYOK capture
28+
* below hung on an unactionable click until the test timed out. The product
29+
* copy is the acceptance result; the oracle follows it.
30+
*
31+
* Scoped to the Execution-mode tablist rather than matching the label
32+
* repo-wide: the BYOK pane renders its own `API protocol` tablist, and the
33+
* media section renders a tab per provider, so an unscoped name match is one
34+
* renamed provider away from resolving to the wrong tab.
35+
*/
36+
function byokModeTab(dialog: Locator): Locator {
37+
return dialog
38+
.getByRole('tablist', { name: 'Execution mode' })
39+
.getByRole('tab', { name: /API providers/i });
40+
}
41+
1942
// The auto-provisioned personal workspace every signed-in identity has. Typed
2043
// against the contract so a new required permission bit or context field fails
2144
// typecheck here instead of drifting into a fixture that silently misrepresents
@@ -164,7 +187,7 @@ test('[P2] captures the settings BYOK surface', async ({ page }) => {
164187
await gotoVisualWorkspace(page);
165188

166189
const dialog = await prepareVisualSettingsDialog(page);
167-
await dialog.getByRole('tab', { name: 'BYOK' }).click();
190+
await byokModeTab(dialog).click();
168191
await expect(dialog.getByRole('tablist', { name: 'API protocol' })).toBeVisible();
169192
await expect(dialog.getByRole('heading', { name: 'Anthropic API' })).toBeVisible();
170193
await waitForVisualFonts(page);
@@ -187,7 +210,7 @@ test('[P2] captures the settings BYOK OpenAI surface', async ({ page }) => {
187210
await gotoVisualWorkspace(page);
188211

189212
const dialog = await prepareVisualSettingsDialog(page);
190-
await dialog.getByRole('tab', { name: 'BYOK' }).click();
213+
await byokModeTab(dialog).click();
191214
await dialog.getByRole('tab', { name: 'OpenAI', exact: true }).click();
192215
await expect(dialog.getByRole('heading', { name: 'OpenAI API' })).toBeVisible();
193216
await waitForVisualFonts(page);
@@ -210,7 +233,7 @@ test('[P2] captures the settings BYOK model dropdown surface', async ({ page })
210233
await gotoVisualWorkspace(page);
211234

212235
const dialog = await prepareVisualSettingsDialog(page);
213-
await dialog.getByRole('tab', { name: 'BYOK' }).click();
236+
await byokModeTab(dialog).click();
214237
await dialog.getByRole('tab', { name: 'OpenAI', exact: true }).click();
215238
const modelSelect = dialog.getByRole('combobox', { name: 'Model', exact: true });
216239
await expect(modelSelect).toBeVisible();

e2e/ui/visual-workspace.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,15 @@ test('[P2] captures the topbar local CLI model list surface', async ({ page }) =
203203

204204
test('[P2] captures the topbar BYOK execution switcher surface', async ({ page }) => {
205205
await configureVisualPage(page, {
206+
// No local agent, which is the premise the popover assertions below already
207+
// state ("a BYOK config has no local agent"). `configureVisualPage`
208+
// otherwise serves `[MOCK_AGENT]` from `/api/agents`, and an installed
209+
// agent wins the popover: it renders that agent's model radiogroup
210+
// (`radio "Default"`) instead of the BYOK provider/model rows, so the
211+
// `.inline-switcher__hint` this case waits for never exists. The chip still
212+
// showed the BYOK glyph and `gpt-4o`, which is why the earlier assertions
213+
// passed and only the popover shape disagreed.
214+
agents: [],
206215
config: {
207216
mode: 'api',
208217
apiKey: 'sk-visual',

0 commit comments

Comments
 (0)