Skip to content

Commit 0d0bab6

Browse files
authored
fix(e2e): read the visible workspace shell while a streamed page arrives (#8951)
* Revert "ci(e2e): temporarily skip tablet workflow-controls mobile check (#8950)" This reverts commit 23b0561. * fix(e2e): read the visible workspace shell while a streamed page arrives On the production build, a query made mid-stream can also match the next segment's shell that React parks in a hidden <div id="S:n"> until it swaps it in, so the class-only locator hit a strict-mode violation in WebKit. Scope the shell to the visible element, and restore the tablet workflow-controls check #8950 skipped for the same cause.
1 parent 23b0561 commit 0d0bab6

1 file changed

Lines changed: 62 additions & 55 deletions

File tree

‎apps/sim/scripts/test-mobile-e2e.ts‎

Lines changed: 62 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,15 @@ async function seed(): Promise<string> {
263263
return encodeURIComponent(`${sessionToken}.${await makeSignature(sessionToken, authSecret)}`)
264264
}
265265

266+
/**
267+
* The rendered workspace content shell. While a streamed page is still arriving, React parks the
268+
* next segment's markup, shell included, in a hidden `<div id="S:n">` until it swaps it in, so the
269+
* class alone can match a second, hidden copy.
270+
*/
271+
function contentShell(page: Page): Locator {
272+
return page.locator('.workspace-content-shell').filter({ visible: true })
273+
}
274+
266275
async function visit(page: Page, route: string) {
267276
const path = `/workspace/${workspaceId}/${route}`
268277
const [response] = await Promise.all([
@@ -280,7 +289,7 @@ async function visit(page: Page, route: string) {
280289
new URL(page.url()).pathname.startsWith(`/workspace/${workspaceId}/`),
281290
'Session was rejected'
282291
)
283-
await expect(page.locator('.workspace-content-shell')).toBeVisible()
292+
await expect(contentShell(page)).toBeVisible()
284293
await expect(page.getByText('Application error', { exact: false })).toHaveCount(0)
285294
const contentByRoute: Record<string, Locator> = {
286295
home: page.getByRole('textbox', { name: 'Message', exact: true }),
@@ -337,7 +346,7 @@ async function expectNoOverflow(page: Page) {
337346
}
338347
)
339348
.toBeLessThanOrEqual(1)
340-
const shell = await page.locator('.workspace-content-shell').boundingBox()
349+
const shell = await contentShell(page).boundingBox()
341350
assert(shell && shell.width > 0 && shell.x >= -1, 'The content must have usable horizontal space')
342351
assert(
343352
shell.x + shell.width <= (page.viewportSize()?.width ?? 0) + 1,
@@ -717,7 +726,7 @@ async function exerciseViewport(
717726
timeout: 120_000,
718727
})
719728
await expect(sidebar).toBeHidden()
720-
await expect(page.locator('.workspace-content-shell')).toBeVisible()
729+
await expect(contentShell(page)).toBeVisible()
721730
await expect(
722731
page.getByRole('button', { name: `Actions for ${folderName}`, exact: true })
723732
).toBeVisible({ timeout: 120_000 })
@@ -1100,61 +1109,59 @@ async function exerciseViewport(
11001109
await expectNoOverflow(page)
11011110
}
11021111
)
1103-
if (viewport.name !== 'tablet') {
1104-
await check(`${prefix}/workflow controls open and return to the canvas`, page, async () => {
1105-
await visit(page, `w/${workflowId}`)
1106-
await expect(page.locator('.react-flow')).toBeVisible({ timeout: 120_000 })
1107-
const panel = page.getByRole('complementary', { name: 'Workflow panel', exact: true })
1108-
await expect(panel).toBeHidden()
1109-
await capture(page, `${browserName}-${viewport.name}-workflow-canvas`)
1110-
await page.getByRole('button', { name: 'Workflow', exact: true }).tap()
1111-
await expectContained(page, panel)
1112-
await capture(page, `${browserName}-${viewport.name}-workflow-panel`)
1113-
await page.getByRole('button', { name: 'Back to canvas', exact: true }).tap()
1114-
await expect(panel).toBeHidden()
1112+
await check(`${prefix}/workflow controls open and return to the canvas`, page, async () => {
1113+
await visit(page, `w/${workflowId}`)
1114+
await expect(page.locator('.react-flow')).toBeVisible({ timeout: 120_000 })
1115+
const panel = page.getByRole('complementary', { name: 'Workflow panel', exact: true })
1116+
await expect(panel).toBeHidden()
1117+
await capture(page, `${browserName}-${viewport.name}-workflow-canvas`)
1118+
await page.getByRole('button', { name: 'Workflow', exact: true }).tap()
1119+
await expectContained(page, panel)
1120+
await capture(page, `${browserName}-${viewport.name}-workflow-panel`)
1121+
await page.getByRole('button', { name: 'Back to canvas', exact: true }).tap()
1122+
await expect(panel).toBeHidden()
1123+
await expect
1124+
.poll(async () => {
1125+
const canvas = await page.locator('.react-flow').boundingBox()
1126+
const content = await contentShell(page).boundingBox()
1127+
return canvas && content ? canvas.width / content.width : 0
1128+
})
1129+
.toBeGreaterThan(0.95)
1130+
if (viewport.name === 'phone') {
1131+
await sql`update workflow set locked = true where id = ${workflowId}`
1132+
await withDocumentNavigation(page, page.url(), () =>
1133+
page.reload({ waitUntil: 'commit', timeout: 360_000 })
1134+
)
1135+
const notifications = page.getByLabel('Notifications', { exact: true })
1136+
await expect(notifications).toContainText('This workflow is locked', {
1137+
timeout: 120_000,
1138+
})
1139+
await expectContained(page, notifications)
1140+
const unlock = notifications.getByRole('button', {
1141+
name: 'Unlock Workflow',
1142+
exact: true,
1143+
})
1144+
await expectContained(page, unlock)
1145+
await capture(page, `${browserName}-phone-workflow-notification`)
1146+
const [unlocked] = await Promise.all([
1147+
page.waitForResponse(
1148+
(response) =>
1149+
response.request().method() === 'PUT' &&
1150+
new URL(response.url()).pathname === `/api/workflows/${workflowId}`,
1151+
{ timeout: 120_000 }
1152+
),
1153+
unlock.tap(),
1154+
])
1155+
assert.equal(unlocked.status(), 200, 'Unlocking the workflow must succeed over HTTP')
11151156
await expect
11161157
.poll(async () => {
1117-
const canvas = await page.locator('.react-flow').boundingBox()
1118-
const content = await page.locator('.workspace-content-shell').boundingBox()
1119-
return canvas && content ? canvas.width / content.width : 0
1120-
})
1121-
.toBeGreaterThan(0.95)
1122-
if (viewport.name === 'phone') {
1123-
await sql`update workflow set locked = true where id = ${workflowId}`
1124-
await withDocumentNavigation(page, page.url(), () =>
1125-
page.reload({ waitUntil: 'commit', timeout: 360_000 })
1126-
)
1127-
const notifications = page.getByLabel('Notifications', { exact: true })
1128-
await expect(notifications).toContainText('This workflow is locked', {
1129-
timeout: 120_000,
1158+
const rows = await sql`select locked from workflow where id = ${workflowId}`
1159+
return rows[0]?.locked
11301160
})
1131-
await expectContained(page, notifications)
1132-
const unlock = notifications.getByRole('button', {
1133-
name: 'Unlock Workflow',
1134-
exact: true,
1135-
})
1136-
await expectContained(page, unlock)
1137-
await capture(page, `${browserName}-phone-workflow-notification`)
1138-
const [unlocked] = await Promise.all([
1139-
page.waitForResponse(
1140-
(response) =>
1141-
response.request().method() === 'PUT' &&
1142-
new URL(response.url()).pathname === `/api/workflows/${workflowId}`,
1143-
{ timeout: 120_000 }
1144-
),
1145-
unlock.tap(),
1146-
])
1147-
assert.equal(unlocked.status(), 200, 'Unlocking the workflow must succeed over HTTP')
1148-
await expect
1149-
.poll(async () => {
1150-
const rows = await sql`select locked from workflow where id = ${workflowId}`
1151-
return rows[0]?.locked
1152-
})
1153-
.toBe(false)
1154-
}
1155-
await expectNoOverflow(page)
1156-
})
1157-
}
1161+
.toBe(false)
1162+
}
1163+
await expectNoOverflow(page)
1164+
})
11581165
}
11591166
} finally {
11601167
documentNavigations.delete(page)

0 commit comments

Comments
 (0)