Skip to content

Commit 52b73ae

Browse files
committed
Merge branch 'wip/fx7-ci-e2e' into wip/platform-org-wiring
2 parents 80726f9 + c90385b commit 52b73ae

13 files changed

Lines changed: 215 additions & 136 deletions

File tree

‎apps/sim/platform/checks/live/flows.spec.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,13 @@ test('Mod+B collapses the sidebar, the choice survives a reload, and Expand rest
6666
await signInAsAdmin(page, liveRoute('/home'))
6767
const sidebar = page.getByRole('complementary', { name: 'Platform sidebar' })
6868
await expect(sidebar).toBeVisible()
69-
// Home's composer takes focus as it mounts and Mod+B leaves a focused field alone, as legacy did.
70-
await expect(page.getByRole('textbox', { name: /Ask about your business/ })).toBeFocused()
69+
// Mod+B leaves a focused field alone, as legacy did, so the press starts off any field. The
70+
// server paints the sidebar before the shortcut listens: press again only while it is showing.
7171
await page.locator('body').click({ position: { x: 700, y: 20 } })
72-
await pressShortcut(page, 'ControlOrMeta+b')
73-
await expect(sidebar).toBeHidden()
72+
await expect(async () => {
73+
if (await sidebar.isVisible()) await pressShortcut(page, 'ControlOrMeta+b')
74+
await expect(sidebar).toBeHidden({ timeout: 1_000 })
75+
}).toPass()
7476
await page.reload()
7577
await expect(sidebar).toBeHidden()
7678
await page.getByRole('button', { name: 'Expand sidebar' }).click()

‎apps/sim/platform/checks/live/home.spec.ts‎

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,15 @@ test.describe('Home for the seeded administrator', () => {
165165
await expect(
166166
page.getByRole('region', { name: 'Needs your attention', exact: true })
167167
).toHaveCount(0)
168-
await page
169-
.getByRole('region', { name: 'Chats', exact: true })
170-
.getByRole('link', { name: 'Vendor renewals' })
171-
.click()
168+
const chats = page.getByRole('region', { name: 'Chats', exact: true })
169+
const vendorRenewals = chats.getByRole('link', { name: 'Vendor renewals' })
170+
// Chats the other live checks start sort above the seeded one, a page or more down the rail.
171+
await expect(async () => {
172+
if ((await vendorRenewals.count()) === 0)
173+
await chats.getByRole('button', { name: 'See more', exact: true }).click()
174+
await expect(vendorRenewals).toBeVisible({ timeout: 1_000 })
175+
}).toPass()
176+
await vendorRenewals.click()
172177
await expect(page).toHaveURL(liveRoutePattern('/search/chat/22222222-'))
173178
await expect(page.getByText('Here is what I found about Vendor renewals.')).toBeVisible()
174179
})
@@ -340,21 +345,21 @@ test.describe('Home with connected accounts', () => {
340345
await withLiveDatabase((sql) =>
341346
sql.begin(async (tx) => {
342347
await tx`insert into permissions (id, user_id, entity_type, entity_id, permission_type)
343-
select ${person.id} || '-' || entity_id, ${person.id}, entity_type, entity_id, permission_type
348+
select ${person.id}::text || '-' || entity_id, ${person.id}, entity_type, entity_id, permission_type
344349
from permissions
345350
where user_id = (select id from "user" where email = ${connectedEmail()})
346351
and entity_type = 'workspace'`
347352
await tx`insert into credential_group_enrollment
348353
select (jsonb_populate_record(null::credential_group_enrollment, to_jsonb(e) || jsonb_build_object(
349-
'id', ${`${person.id}-enrollment`}, 'user_id', ${person.id}, 'email', ${person.email},
350-
'invitation_token_hash', ${`${person.id}-token`}))).*
354+
'id', ${`${person.id}-enrollment`}::text, 'user_id', ${person.id}::text,
355+
'email', ${person.email}::text, 'invitation_token_hash', ${`${person.id}-token`}::text))).*
351356
from credential_group_enrollment e where e.id = 'platform-live-enrollment'`
352357
for (const grant of [GRANT, CALENDAR_GRANT]) {
353358
await tx`insert into credential
354359
select (jsonb_populate_record(null::credential, to_jsonb(c) || jsonb_build_object(
355-
'id', ${`${person.id}-${grant}`}, 'created_by', ${person.id},
356-
'credential_group_enrollment_id', ${`${person.id}-enrollment`},
357-
'display_name', ${person.email}))).*
360+
'id', ${`${person.id}-${grant}`}::text, 'created_by', ${person.id}::text,
361+
'credential_group_enrollment_id', ${`${person.id}-enrollment`}::text,
362+
'display_name', ${person.email}::text))).*
358363
from credential c where c.id = ${grant}`
359364
}
360365
})

‎apps/sim/platform/checks/live/redirects.spec.ts‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,8 @@ const REDIRECTS: readonly (readonly [string, string])[] = [
2424
[liveRoute('/links?search=hand'), liveRoute('/settings/links?search=hand')],
2525
[`${WORKSPACE}/w`, `${WORKSPACE}/workflows`],
2626
[`${WORKSPACE}/home`, liveRoute('/home')],
27-
// The upgrade prompt carries why it was shown and nothing else.
28-
[
29-
`${WORKSPACE}/upgrade?reason=credits&ref=x`,
30-
liveRoute('/settings/billing/plans?reason=credits'),
31-
],
27+
// The seeded organization is on Enterprise, whose plans page exits to Billing, as legacy's did.
28+
[`${WORKSPACE}/upgrade?reason=credits&ref=x`, liveRoute('/settings/billing')],
3229
]
3330

3431
for (const [from, to] of REDIRECTS) {
@@ -62,7 +59,11 @@ const QUERY_REDIRECTS: readonly (readonly [string, string])[] = [
6259
[`${WORKSPACE}/settings/apikeys?from=mail`, `${WORKSPACE}/settings/api-keys?from=mail`],
6360
[
6461
`${WORKSPACE}/access-requests?requestId=req-1`,
65-
`${WORKSPACE}/settings/requests?requestId=req-1`,
62+
`${WORKSPACE}/settings/requests?view=requests&requestId=req-1`,
63+
],
64+
[
65+
`${WORKSPACE}/upgrade?reason=credits&ref=x`,
66+
liveRoute('/settings/billing/plans?reason=credits&ref=x'),
6667
],
6768
]
6869

@@ -112,14 +113,19 @@ test('every legacy sub-path of a workspace the viewer cannot open is a branded n
112113
}
113114
})
114115

115-
test('an organization the viewer does not belong to is the branded not-found, not a failed read', async ({
116+
test('an organization the viewer does not belong to is the access denial, not a failed read', async ({
116117
page,
117118
}) => {
118119
await signInAsAdmin(page, liveRoute('/home'))
119120
const outside = `/o/${organizationId()}-outside`
120121
for (const route of ['/home', '/settings/members']) {
121122
await page.goto(`${outside}${route}`)
122-
await expect(page.getByRole('heading', { name: 'Page not found' }), route).toBeVisible()
123+
await expect(
124+
page.getByRole('heading', { name: 'Organization access denied' }),
125+
route
126+
).toBeVisible()
127+
await expect(page.getByRole('button', { name: 'View your workspaces' }), route).toBeVisible()
128+
await expect(page.getByRole('heading', { name: 'Something went wrong' }), route).toHaveCount(0)
123129
}
124130
})
125131

‎apps/sim/platform/checks/live/self-hosted/surfaces.spec.ts‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { expect } from '@playwright/test'
2+
import { SIM_SITE_URL } from '@sim/utils/site'
23
import { signInAsAdmin } from '@/platform/checks/live/session'
34
import { firstHopFrom } from '@/platform/checks/live/support/navigation'
45
import { liveRoute, liveRoutePattern } from '@/platform/checks/routes'
@@ -30,15 +31,19 @@ test('a self-hosted shell renders every section a member uses', async ({ page })
3031
}
3132
})
3233

33-
test('self-hosted settings offer no billing section, and a saved billing link is not found', async ({
34+
test('self-hosted settings offer no billing section, a saved billing link is not found, and plans go to the hosted app', async ({
3435
page,
3536
}) => {
3637
await signInAsAdmin(page, liveRoute('/settings'))
3738
await expect(page.getByRole('link', { name: 'Billing', exact: true })).toHaveCount(0)
38-
for (const route of ['/settings/billing', '/settings/billing/plans']) {
39-
await page.goto(liveRoute(route))
40-
await expect(page.getByRole('heading', { name: 'Page not found' }), route).toBeVisible()
41-
}
39+
await page.goto(liveRoute('/settings/billing'))
40+
await expect(page.getByRole('heading', { name: 'Page not found' })).toBeVisible()
41+
// A self-hosted deployment buys its plan on the hosted app, as the legacy upgrade page sent it.
42+
await page.route(`${SIM_SITE_URL}/**`, (route) =>
43+
route.fulfill({ status: 200, contentType: 'text/html', body: 'Hosted upgrade stand-in' })
44+
)
45+
await page.goto(liveRoute('/settings/billing/plans?reason=credits'))
46+
await expect(page).toHaveURL(`${SIM_SITE_URL}/upgrade?reason=credits`)
4247
})
4348

4449
for (const [from, to] of [

‎apps/sim/platform/checks/live/support/database.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,18 @@ export async function seedPerson(
6262
*/
6363
async function removePerson(person: SeededPerson) {
6464
await withLiveDatabase(async (sql) => {
65-
await sql`delete from organization where kind = 'personal' and id in (
66-
select organization_id from member where user_id = ${person.id})`
67-
const owned = await sql<{ id: string; projectId: string | null }[]>`
68-
select id, project_id as "projectId" from workspace where owner_id = ${person.id}`
65+
const owned = await sql<{ id: string }[]>`
66+
select id from workspace where owner_id = ${person.id}`
6967
if (owned.length > 0) {
7068
const workspaceIds = owned.map(({ id }) => id)
7169
await sql`delete from permissions
7270
where entity_type = 'workspace' and entity_id in ${sql(workspaceIds)}`
7371
await sql`delete from workspace where id in ${sql(workspaceIds)}`
74-
const projects = owned.flatMap(({ projectId }) => (projectId ? [projectId] : []))
75-
if (projects.length > 0) await sql`delete from project where id in ${sql(projects)}`
7672
}
73+
// A project restricts deleting its organization and its owner, so it goes before both.
74+
await sql`delete from project where owner_id = ${person.id}`
75+
await sql`delete from organization where kind = 'personal' and id in (
76+
select organization_id from member where user_id = ${person.id})`
7777
await sql`delete from "user" where id = ${person.id}`
7878
})
7979
}

‎apps/sim/platform/styles/bootstrap.css‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,10 @@
1414
[data-sim-desktop-title-bar] [data-desktop-drag] :is(button, a, input, textarea, [role="tab"]) {
1515
-webkit-app-region: no-drag;
1616
}
17+
18+
/* iOS zooms the page into a focused text field set below 16px, so a touch phone reads them at 16px. */
19+
@media (pointer: coarse) and (width < 48rem) {
20+
[data-emcn] :is(input, textarea)[data-type] {
21+
font-size: var(--text-reading);
22+
}
23+
}

‎apps/sim/scripts/org-redesign-e2e/checks/home.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ export async function homeChecks() {
285285
})
286286
)
287287
})
288-
await check('Recents collapse repeats, and every chip has a mark', async () => {
288+
await check('Recents collapse repeats, and only a provider chip has a mark', async () => {
289289
const [before] =
290290
await sql`select sources from organization_search_history where user_id = ${ADMIN.userId} and organization_id = ${ORGANIZATION_ID}`
291291
const viewedAt = new Date().toISOString()
@@ -317,9 +317,9 @@ export async function homeChecks() {
317317
sources[0].url,
318318
'A collapsed chip must link its latest visit'
319319
)
320-
for (const link of await links.all()) {
321-
await expect(link.locator('svg, img').first()).toBeVisible()
322-
}
320+
// The design owner's row draws a provider's mark and no favicon for a plain site.
321+
await expect(links.first().locator('svg, img').first()).toBeVisible()
322+
await expect(links.nth(1).locator('svg, img')).toHaveCount(0)
323323
const channelChip = links.first()
324324
await channelChip.hover()
325325
await recents.getByRole('button', { name: 'Remove #candidates from recents' }).click()

‎apps/sim/scripts/org-redesign-e2e/checks/landing.ts‎

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,26 +73,23 @@ export async function landingChecks() {
7373
})
7474
})
7575
await check(
76-
"a member's agent chat opens as a chat, is absent under Search, and lists in the rail",
76+
"a member's agent chat opens as a chat, Search hands it back to the chat, and it lists in the rail",
7777
async () => {
7878
await withSession(MEMBER, async (session) => {
7979
const { page } = session
8080
const transcript = page.getByText(`Here is what I found about ${MEMBER_CHATS.agent.title}.`)
8181
await page.goto(url(org.chat(MEMBER_CHATS.agent.id)))
8282
await expect(page).toHaveURL(url(org.chat(MEMBER_CHATS.agent.id)))
8383
await expect(transcript).toBeVisible()
84-
// Search only opens assistant chats, so an agent chat there is absent, as in legacy.
84+
// Search opens only assistant chats; a Search URL for an agent chat returns to the chat.
8585
await page.goto(url(org.searchChat(MEMBER_CHATS.agent.id)))
86-
await expectPageNotFound(page)
87-
await expect(transcript).toHaveCount(0)
86+
await expect(page).toHaveURL(url(org.chat(MEMBER_CHATS.agent.id)))
87+
await expect(transcript).toBeVisible()
88+
await expect(searchField(page)).toHaveCount(0)
8889
await page.goto(url(org.home))
8990
await session.settle()
9091
await expect(railLink(page, MEMBER_CHATS.agent.title)).toBeVisible()
9192
})
92-
},
93-
{
94-
// The member's own agent chat, which Search does not open.
95-
allow: [refused(404, 'GET', org.searchChat(MEMBER_CHATS.agent.id))],
9693
}
9794
)
9895
await check(

‎apps/sim/scripts/org-redesign-e2e/checks/sidebar.ts‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,13 @@ export async function sidebarChecks() {
100100
await check('hover warms Integrations through its own reads', async () => {
101101
await withSession(MEMBER, async (session) => {
102102
const { page } = session
103-
await page.goto(url(org.home))
103+
// Home reads the member's accounts itself, so the hover starts from Search, which does not.
104+
await page.goto(url(org.search))
104105
await session.settle()
106+
assert(
107+
!session.requests.some(({ path }) => path.includes('/connected-accounts')),
108+
'Search must not read the accounts the hover is meant to warm'
109+
)
105110
session.requests.length = 0
106111
await mainNav(page).getByRole('link', { name: 'Integrations', exact: true }).hover()
107112
await expect
@@ -134,11 +139,13 @@ export async function sidebarChecks() {
134139
await page.reload()
135140
await expect(rail).toBeHidden()
136141
await expect(opener).toBeVisible()
137-
// The server renders the attribute, so only this says the shortcut is listening.
138-
await expect(composer(page)).toBeFocused()
142+
// The server renders the collapsed rail before the shortcut listens, so a press before
143+
// hydration does nothing: press again only while the rail is still hidden.
139144
await page.locator('body').click({ position: { x: 700, y: 20 } })
140-
await page.keyboard.press('Control+b')
141-
await expect(rail).toBeVisible()
145+
await expect(async () => {
146+
if (await rail.isHidden()) await page.keyboard.press('Control+b')
147+
await expect(rail).toBeVisible({ timeout: 1_000 })
148+
}).toPass()
142149
await composer(page).click()
143150
await page.keyboard.press('Control+b')
144151
await expect(rail).toBeVisible()

‎apps/sim/scripts/test-assistant-actions-e2e.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,8 @@ try {
608608
await expect(connectIntegration).toHaveCount(0)
609609
const policy = page.getByRole('group', { name: 'Create Task in Salesforce permission' })
610610
await expect(policy).toBeVisible({ timeout: 180_000 })
611-
await expect(page.getByRole('button', { name: 'Reconnect', exact: true })).toBeVisible()
611+
// This deployment has no Salesforce or Jira OAuth client, so its accounts cannot reconnect.
612+
await expect(page.getByRole('button', { name: 'Reconnect', exact: true })).toHaveCount(0)
612613
await capture('integration-account-actions')
613614
await page.getByRole('button', { name: 'Disconnect', exact: true }).click()
614615
const confirmation = page.getByRole('dialog', {
@@ -704,7 +705,7 @@ try {
704705
await expect(
705706
page.getByRole('heading', { name: 'launch-team@acme.example', exact: true })
706707
).toBeVisible()
707-
await expect(page.getByRole('button', { name: 'Reconnect', exact: true })).toBeVisible()
708+
await expect(page.getByRole('button', { name: 'Reconnect', exact: true })).toHaveCount(0)
708709
await capture('integration-selected-account-actions')
709710
await page.getByRole('button', { name: 'Disconnect', exact: true }).click()
710711
const confirmation = page.getByRole('dialog', {

0 commit comments

Comments
 (0)