Skip to content

test(e2e): workplace and concierge [2026-09-18] - #497

Open
sharmilaseenivasan17 wants to merge 32 commits into
developfrom
test/e2e-workplace-concierge-2026-09-18
Open

sharmilaseenivasan17 wants to merge 32 commits into
developfrom
test/e2e-workplace-concierge-2026-09-18

Conversation

@sharmilaseenivasan17

Copy link
Copy Markdown

Added E2E tests for Concierge visitor invitations, notes and induction, parking management, desk QR codes, catering, deals, and email templates. Added partial coverage for Points and Facilities, plus a survey API test while survey creation through the UI remains blocked by a bug. Updated the Excel plans with coverage counts, simple bug examples, and suggested decisions.

sharmilaseenivasan17 and others added 11 commits September 15, 2026 16:02
23 tests across 11 spec files, mirroring the existing desk coverage:
invite (single and group), cancel from the app, form validation, settings,
visitor details, times, booking for a colleague, check-in/check-out,
editing, and visibility between users.

22 pass; 1 is fixme, blocked on an app bug where saving an edit in
single-visitor mode throws and sends nothing.

Visitor support code lives in e2e/support/visitor/ so the desk specs cannot
be affected - no shared support file is changed. Also adds e2e/tsconfig.json
and a bun run e2e:typecheck script; nothing type-checked the specs before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
14 tests across 6 spec files, in the same shape as the desk and visitor
coverage: book through the UI, teardown, double-booking (same slot and
partial overlap, as a second user, with a control), visibility between
users, times and the limits on what may be chosen, attendees, and
cancelling from the app.

11 pass; 3 are fixme, blocked on app bugs - a room booking is stored with
no zones (ROOM-B2), the meeting length picked is not the length booked
(ROOM-B3), and cancelling from the schedule fires DELETE /events and 500s
while the room stays held (ROOM-B4).

Rooms only run locally in one mode. By default the meeting flow calls
Microsoft/Google through /events and /calendars, which 500 on the local
stack; with app.events.use_bookings = true the same form saves an ordinary
PlaceOS booking of type room, with no outbound call. So a green run proves
the PlaceOS-native room path works and says nothing about the calendar
path - E2E_USER_STORIES.md WP-E2E-15 is now partial rather than out of
scope, split along that line, and rooms have their own section 1b.

Room support code lives in e2e/support/room/ so neither the desk nor the
visitor specs can be affected, and rooms are seeded there rather than in
the shared seed.ts - a room is an engine System and must genuinely be
created. The one existing file touched is your-bookings.page.ts, where the
constructor argument becomes protected so the room schedule page can
inherit it instead of copying it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… for rooms

Seven more room tests, taking the room files from 14 tests to 22 - 17
passing, 5 blocked, 0 failing. Four new spec files:

  room-edit        change the start time; move the booking to another room
  room-capacity    strict mode refuses before anything is sent; the default
                   only warns and still books
  room-favourites  a favourite is saved to the user's own settings, and the
                   Favorites Only filter narrows the picker to it
  room-approval    the default stores the booking unapproved
  room-catering    fixme, see ROOM-B5 below

The capacity pair needs a room too small to book, and capacity belongs to
the engine System rather than to settings, so room.seed.ts now creates
three rooms per worker: a normal one, an alt one to move a booking into,
and a capacity-1 one. catering.seed.ts does the same for a catering menu,
which is made of assets - a hidden _CATERING_ category, a CATERING: asset
type and one asset on the building - all on the engine api.

ROOM-B5, a new finding: catering cannot be ordered with a PlaceOS-native
room booking at all. The meeting saves (201) and the order that follows is
refused 422 "error linking booking to event", because orders are linked to
a calendar event by id and in use_bookings mode that id is a booking id.
The room booking is then left behind undeleted and no order exists, while
the user is shown an error and has every reason to believe nothing was
booked. ROOM-B1 was re-measured through the app and still 500s.

Checking in to a room is blocked by the stack, not by effort, and has no
spec on purpose: the control needs a live Bookings driver module on the
room's System, and this stack has one driver (spec_helper) and one module
(PrivateHelper). ROOM-23 records that, and what would unblock it.

E2E_USER_STORIES.md carries ROOM-15 ... ROOM-23 and the fifth finding.
your-bookings.page.ts gains two protected hooks so the room schedule page
inherits startEdit rather than copying it - the form Edit lands on is the
only part that differs by booking type.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
24 tests in 15 new spec files, all passing: 14 for the Your Bookings page,
which had no coverage of its own, and 10 filling the desk gaps.

Your Bookings (e2e/support/bookings/, bookings-*.spec.ts)
  the listing, per type and per day, plus what it asks the backend for
  the five type filters, and the chips beside them
  cancelling from the list, and declining the confirmation
  Edit routing to the right form per booking type
  checking in to a desk booking and back out
  a finished booking offers no check-in
  an empty day shows its empty state
  another user's bookings never appear, with a control

Desks (e2e/support/desk/, desk-*.spec.ts)
  the day, start time and length chosen are the ones stored
  a maximum length and bookable hours limit what is offered
  editing the time, and moving the booking to another desk
  an all-day booking is stored as all-day
  booking for a colleague stores them as user and you as booker
  a favourite desk is saved against the user
  a bad booking request is refused 4xx, never 5xx
  a clash check uses the current booking_end (REG-03, now covered)
  the checked-in badge appears only once checked in (REG-04)

Both areas keep their own support folder and share nothing with each other
or with visitor/ and room/. The schedule page object still belongs to
visitor/your-bookings.page.ts, which introduced it; bookings/ and desk/
inherit it, and the form Edit lands on is now a settable hook because on
that page it depends on the booking rather than the page.

Three things measured along the way, recorded where they cost time:

- The schedule sends include_deleted=true and renders cancelled bookings
  for ever, so the number of cards on a day grows with every run. Card
  counts are therefore never asserted; the specs compare against what the
  backend says is live.
- POST /bookings can return 201 with an id for a booking that does not
  exist: GET on that id then 404s. That is REG-09 doing more damage than
  its row describes, and every create here reads the row back.
- The desk host field searches /api/staff/v1/people, the calendar
  directory, which 500s on this stack. app.basic_user_search switches it
  to the local user list, so DESK-14 covers the PlaceOS path only.

Also: the desk form's date picker needs a converging open (the form is
rebuilt underneath the click), and the schedule's sidebar calendar only
reaches the displayed month - so desk slots stay within four days of today
and separate by hour instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
14 tests in 8 spec files: 13 passing, 1 blocked. Parking was the last
booking type in workplace with no coverage at all.

  parking-booking      book through the full UI; teardown really tears down
  parking-clash        same window and partial overlap refused 409 as a
                       second user, with a control; the space frees up again
  parking-scoping      another user cannot see or delete yours, with a control
  parking-times        the day, start and length chosen are the ones stored;
                       limits control what is offered
  parking-cancel       cancelling from the app really removes it; declining
                       the confirmation does not
  parking-edit         change the time; move the booking to another space
  parking-favourites   a favourite space is saved against the user
  parking-api          a bad request is refused 4xx, never 5xx
  parking-restrictions fixme - PARK-B1 below

Parking needed the most seeding of any resource here, and none of it is in
the shared seed.ts: parking.seed.ts creates a level zone tagged `parking`,
the hidden _PARKING_ asset category, the _PARKING_SPACES_ asset type, and
one space per worker plus a spare. A NEW level zone rather than a tag on
the seeded one, so the desks' data is untouched.

PARK-B1, a new finding: with `parking.require_space_restriction` on, the
ordinary parking booking form cannot be submitted at all. The validator
lives in the shared booking form and fires for any parking booking, while
the parking booking form never renders a `space_restrictions` control - so
Confirm Reservation answers "Some fields are invalid.
[space_restrictions]" naming a field that is not on screen. The setting's
own schema describes it as belonging to the parking REQUEST flow, which
does render one. Measured on this stack with no overrides, which blocked
the whole area until the setting was turned off in PARKING_BASE_SETTINGS.

Also recorded where it cost time: the parking confirm step is a BOTTOM
SHEET in the CDK overlay, not a routed view like the desk and meeting
flows; the picker's favourite control is a bare `fav` attribute; and the
favourites key is `favourite_parking_spaces`, not the `favourite_parking`
constant in libs/common that nothing reads.

The backend accepts a booking against an asset id that does not exist, for
parking and for desks alike. Recorded in both API specs as a warning
rather than a failure - it may be worth a bug of its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
11 tests in 6 spec files: 8 passing, 3 blocked. The home page had nothing
of its own - boot.spec.ts proves the shell renders, which says nothing
about whether the panels show the right data.

  home-upcoming     a booking later today is listed; View all hands off to
                    Your Bookings with the booking findable there
  home-colleagues   a colleague added from the sidebar is saved; removing
                    one clears it
  home-favourites   a favourite desk is listed on the Favourites tab;
                    removing it there clears the saved setting
  home-scoping      another user's booking never appears, with a control
  home-quick-book   fixme - HOME-B2 below
  home-availability fixme - blocked by the stack, below

Three things measured that shaped the tests:

- "Upcoming" means TODAY and nothing else. The panel is built from the
  schedule's own list filtered to `isSameDay(date, now)` and sliced to
  five. A first draft booked sixteen days out and spent three failures
  finding that out.

- HOME-B1, a new finding: the panel keeps showing bookings you cancelled.
  It inherits the schedule's include_deleted=true query and only hides
  what was cancelled in the current session. Measured: five cards, every
  one cancelled, while a live booking for the same user and day was
  absent - the junk had crowded it out of the five slots. So it both tells
  users they have a desk they cancelled AND can hide a real one.

- HOME-B2, a new finding: the one-click quick-book tile spins for ever and
  books nothing. GET /calendars 500s here (the calendar surface this suite
  does not cover), and `book()` awaits listAvailableResources outside any
  try/catch - so the rejection kills the handler after the loading flag is
  set, leaving a permanent spinner and no message. A misconfigured tenant
  would look like this in production.

The availability panel is blocked by the stack rather than by a bug: it
lists only rooms a live driver binding reports as free, and this stack has
one driver (spec_helper) and one module (PrivateHelper). Same blocker as
room check-in (ROOM-23).

Also recorded where each cost a run: the colleague list is its own
`contacts` metadata document, NOT the favourite_team_members key that
libs/common advertises; the sidebar tabs are reached by their material
ligatures (people, favorite) because neither button has a name; and
colleague rows are labelled with display names, so lookups normalise both
sides before comparing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nine more tests: 3 passing, 6 written and skipped against defects they
guard. Plus a REG-09 retry for the room booking flow, which was the last
place in the suite without one.

Passing
  parking-levels    a space on a level that is not tagged `parking` is not
                    offered, with a control that the properly-placed one is

Guards for findings, written and skipped
  room-multi        ROOM-B6 (new): a two-room meeting books ONE room. Both
                    rooms show on the form, the meeting is accepted, and
                    only the first is held - so the second stays bookable
                    and everyone sent to it finds it occupied.
  room-allday       ROOM-B7 (new): the all-day flag is ignored. With the
                    checkbox proven still ticked at send time, the stored
                    booking is one hour long.
  room-delegate     ROOM-B8 (new): the chosen host is discarded. With the
                    field proven to still show the colleague at send time,
                    the booking comes back owned by the booker. Desks get
                    this right (DESK-14 is green), which is the useful
                    comparison.
  visitor-duplicate VIS-B1's guard. Red-checked: the second identical
                    invite returns 201.
  visitor-group-clash VIS-B9's guard. Red-checked: 409 Conflicting booking
                    on a `group` row named `${host}[${creation date}]`,
                    which is the mechanism itself, visible in the response.
  home-upcoming     HOME-B1's second facet: a live booking is crowded off
                    the five-slot panel by cancelled ones.

All three new room findings were proven to be the app discarding input
rather than the form reverting it: `bookRoomViaUI` now asserts the All Day
checkbox and the host field still hold their values immediately before the
meeting is confirmed. Without that, "the booking came back wrong" has two
indistinguishable causes and neither could be reported honestly.

Two things that shaped the tests rather than the app:

- The meeting form's all-day control is `events.allow_all_day`, in
  meeting-form-details.component.ts - NOT `allow_multiday`, which only
  widens the picker's dates. A first attempt looked for a checkbox that
  was never going to be there.
- Multi-select rooms confirm with `space-return` and the add button starts
  DISABLED, enabling only once a row click registers. Checking isEnabled
  immediately silently adds one room instead of two.

The home page's Upcoming panel cannot be relied on to show a live booking
while HOME-B1 stands, so the scoping control proves the same property on
Your Bookings instead of losing the control altogether.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ures

Five more workplace tests: 4 passing, 1 parked.

  parking-access    a user marked `deny` cannot book, and can again once the
                    flag is cleared. The flag is an asset, and its value is
                    the STRING 'true' - a real boolean never registers.
  parking-request   a submitted request is stored as a parking booking
                    against an `unallocated-*` asset, the only parking
                    booking that holds no real space.
  desk-scoping      DESK-15, which was blocked on a seeding change. The
                    extra level is created by the spec and deleted
                    afterwards, so the shared seed.ts stays untouched.
  room-features     the picker's facilities filter narrows to the room that
                    has the feature. room.seed.ts now puts one feature on
                    the `alt` room, because the filter section is not
                    rendered at all when no room carries anything.
  home-meeting-with the colleague shortcut opens the meeting form with that
                    person already invited.
  home-errors       fixme: the home page raises an unhandled rejection on
                    load from the calendar 500s (HOME-B2).
  room-rules        fixme, and UNRESOLVED: a `hidden` booking rule has no
                    effect, and I have not established whether the ruleset
                    shape is wrong or the picker ignores it. The docblock
                    records what was measured and the one-run way to settle
                    it (window.debug_booking_rules).

Two API details learned and written down: a System PATCH needs `version`
in the QUERY STRING (in the body it is ignored and the request 422s), and
booking rules are read from the BUILDING's own metadata document even
though the request reads like a query about its children.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Passing. A group invite is a container plus one booking per person, so an
edit that updated only the container, or only the member it was opened
from, would leave half a party expected at the old time and reception
turning people away.

Three things this cost, all now written into the spec:

- inviteVisitorsViaUI returns the CONTAINER as well as the members - three
  bookings for two visitors - so the members are picked out by asset
  address.
- The container has to be swept in teardown as well as the members. It is
  not returned by the flow, and because every group invite a host makes in
  a day shares one asset id (VIS-B9), one leftover container makes the next
  three runs of this test impossible.
- The flow's `date` option chooses a DAY, not a time, so the starting hour
  is read back rather than assumed.

Also corrected visitor-group-clash to build its container id the way the
app does - `${host}[${YYYY-MM-DD}]`, measured from a real invite - rather
than with toDateString().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
frontend-templates Ignored Ignored Preview Oct 8, 2026 2:17am UTC

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@w-le
w-le requested review from MrYuion and camreeves and removed request for w-le September 22, 2026 05:27
@w-le
w-le marked this pull request as draft September 22, 2026 05:49
@camreeves

camreeves commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Thanks Sharmila, this is a lot of solid work, and it has already earned its keep: most of the 13 bugs you raised on the 22nd were fixed by Alex within a few days because of it.

I reviewed it against the AUTOTEST pages and PPT-2665, and ran it the way CI will once it's merged: 704a960 merged with today's develop, a fresh stack, the same backend images the runner uses (placeos-2.2607.2), TZ=Etc/UTC, 2 workers and CI=true. One commit has landed since (535c1d5, favourite room booking from home). It isn't in these numbers.

Workplace, which is what CI will run: 138 tests, 112 passed, 3 failed, 2 flaky, 21 skipped (7 minutes). Concierge isn't in the CI workflow, so it won't run after merge either (more on that below).

Every red CI run posts an alert to chat, so the suite needs to be green before it goes in. This is what's needed.

Before merge

  • Calendar helper can't change month. pickCalendarDay (e2e/support/visitor/calendar.ts) throws for any date outside the displayed 6-week grid. YB-11 (+13 days) and YB-12 (+15 days) fail from roughly the 24th to the 28th of every month, and at some month ends the cancel, edit and scoping specs join them. The error message already names the fix: click the month chevrons (schedule-next-month / schedule-previous-month, whose names are swapped in the app) until the date is on screen, then re-read the header.
  • ROOM-25b depends on the machine's timezone. It builds 14:00 and the weekday bit in the process timezone but sends timezone: 'Australia/Sydney'. Under UTC (CI) that is midnight the next day in Sydney, so week 0 never comes back. Same stack, same code: it passes with TZ=Australia/Sydney and fails with TZ=Etc/UTC. Build the date in Sydney time.
  • Seeding races. parking.seed.ts and room.seed.ts (ensureRooms) list then create, and fail with 422 ... should be unique when two workers seed at the same moment. I hit both at 2 workers (PARK-14 in the full run, ROOM-25 in a room-only run). concierge.seed.ts already handles this with alreadyExists(); use the same approach.
  • DESK-GROUP-03 is flaky. It needed a retry in 3 of my last 4 runs. Twice it failed because mat-snack-bar-container matched two elements: the previous warning still animating out (it carries mat-exit) and the new one. Scope the locator to the new message, by its text or by excluding [mat-exit]. Once it failed differently, with the confirm button never appearing, so check the trace again after that change.
  • DESK-21 as test.fixme for now. It's a real bug (Require locker comes back ticked after the metadata reload), but it only fails some of the time: 2 of 10 runs on this branch. The AUTOTEST pages say a flaky test gets fixed rather than tolerated, and here the fix belongs in the app. It's the Require locker part of PPT-2643, which is in Testing, so add the DESK-21 result there and move it back to In Progress. (The checkbox writes secondary_resource straight to the model, so the PPT-2643 fix never captures it.)
  • Merge develop into this branch and drop the guards that now pass. A normal merge and push, no rebase or force-push; it merges cleanly. With the fixme removed, these pass on today's develop (including fix(bookings): save full day for all-day room bookings (PPT-2804) #498), which also counts as the Testing check for each ticket:
    • DESK-GROUP-04 (PPT-2798), HOME-04 and HOME-08 (PPT-2799), HOME-15 (PPT-2800)
    • ROOM-13 (PPT-2802), ROOM-11 (PPT-2809), ROOM-14 (PPT-2810), VIS-15 (PPT-2807)
    • ROOM-21 (the 500 came from the empty zones, which the PPT-2802 change now fills)
    • ROOM-27 (PPT-2804). Alex's fix(bookings): save full day for all-day room bookings (PPT-2804) #498 was merged today, so tighten it from "at least 8 hours" to the full day with all_day set.
  • Fix the tests that can't see their fix yet:
    • HOME-09 (PPT-2800): the tile now opens its confirmation ("Would you like to book the desk for ...? Cancel / Accept") instead of spinning. The test never presses Accept, so no booking is sent.
    • VIS-25 (PPT-2808): the test posts the group container itself with the old ${email}[${date}] id, so it reproduces the old bug whatever the app does. Alex's fix gives each group a grp-<random> id. Create both groups through the invite form instead.
    • ROOM-25 (PPT-2806): it stops at the recurrence menu. The page snapshot shows "Weekly on Saturday", but mat-option filtered by /^Weekly on/ doesn't find it.
    • ROOM-30: the spec header still says the rule setup is unresolved, and the corrected setup from your temporary run isn't in the PR. Commit it, then drop the guard.
  • Keep ROOM-28 as fixme and fail PPT-2805 back to Alex. On develop the host field shows the colleague right up to sending, but the saved booking has user_email set to the person who filled in the form. Alex's fix is in the event-to-booking conversion; in the form, the chosen host never reaches the event.
  • Commit locker-booking.spec.ts with LOCK-01/03/04 as fixme, and raise the bug in Jira for Alex: locker-list-field.component.ts:185 passes items: this.items (the signal itself) and locker-select-modal.component.ts:242 spreads it, so the dialog throws and opens empty. Workplace locker booking is broken on develop.

Once that's pushed I'll run the e2e workflow on this branch by hand, and we'll merge when it's green.

Concierge (next PR)

Concierge isn't in the CI workflow, and it can't be added as it stands: 7 of its tests only pass after the workplace suite has already run on the same stack. On a fresh stack they fail, on develop and on this branch alike.

  • CON-ASSET-01 falls back to the workplace seeder, which reads the workplace token file (e2e/.auth/admin-0.token.json), so every call 404s.
  • CON-CAT-01 needs the catering menu, which only the (fixme) workplace catering spec seeds.
  • CON-CAT-03 needs the Workplace dev server as well.
  • CON-DAY-08 and CON-ROOM-01/02/03 need rooms, which only the workplace room seeder creates. With no rooms the day view never calls /events.

Seeding through the concierge adminApi fixture, and adding the Workplace dev server as a second webServer entry in the concierge config, would fix all seven.

Also for a follow-up

  • Coverage contract. E2E_USER_STORIES.md still only has rows for visitors and rooms. "Convert your manual tests" asks for every test to have a line there, along with the keep-manual and blocked rows, because a decision that only lives in a spreadsheet gets lost. The E2E Coverage tab in your sheet is most of the content already.
  • Calendar and directory scenarios (CON-DAY-02 to 07, CON-EVT-01, CON-REP-02, CON-STAFF-*, ROOM-CALENDAR-CATERING) depend on a real outside service, which makes them "keep manual" under that page. Record them that way rather than as blocked.
  • AUTH-E2E-03 proves each refresh issues new tokens that work, but never checks that the old refresh token is refused afterwards, which is the point of rotation. One more request with rt0 expecting a 4xx would close it.
  • DESK-GROUP-02 accepts either a snackbar or a disabled button for "no colleagues". Pin it to what the app does today so a change gets noticed.

The two favourites commits from this morning look good, and both tests pass in the CI-style run.

@MrYuion MrYuion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of the e2e suite. Most findings are races between parallel workers or tests that pass without checking anything. Items on the seed, scoping, and token files will likely cause intermittent CI failures.

);
}
const zones = [building.id, level?.id].filter(Boolean) as string[];
const existing = await listSystems(admin);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensureRooms caches only inside one process. Each Playwright worker does its own list-then-create, so on an empty stack (after down.sh --volumes) 4 workers each see no systems and each create all 12 rooms. You then get duplicate E2E Room N systems (and the name-based picker books the wrong id) or 422 errors that fail the losing workers. "The first caller creates, everyone after finds" is true only within one worker. parking.seed.ts and asset.seed.ts use the same pattern.

Suggest: seed in globalSetup, or use a cross-process lock file.

// Their desk, their booking, cleared by THEM: `GET /bookings` is
// caller-scoped, so this worker's sweep cannot see it and the desk would
// stay held by something invisible.
await releaseFor(other, 'desk', their_desk.id, from, to);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With fullyParallel, worker A signs in as staff-B and clears deskFor(B) for all of SCHEDULE_DAYS.scoping, before and after the test. At the same time, worker B's "control: your own booking" test books that same desk on day 9 at 14:00. A's sweep can delete B's control booking, so B's card never appears and the test fails intermittently.

Suggest: give this test its own desk, or its own day, that no other spec uses.


// Their desk, their booking, swept by THEM: `GET /bookings` is
// caller-scoped, so this worker cannot see or clear it.
await releaseAsset(other, 'desk', their_desk.id, start - 60, start + 3600);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test books and clears worker B's own desk for today, signed in as staff-B. B's desk-booking.spec books the same desk today and clears ±2 days. Result: a 409 clash on A's createBookingViaApi, or one worker's sweep deletes the other worker's live booking in the middle of a test.

Suggest: use a dedicated desk for the scoping specs.

await schedule.showDayOf(target.date_ms);
await schedule.waitForLoaded();

const for_day = asked.filter(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard can never fail. for_day drops any request whose window extends past target ±1 day, so a wide request (for example 14 days, with period_start = target - 7d) never reaches the to - from <= 2 * DAY check. If the page regresses to a wide window, the test still passes and does not catch the 100-row listing-limit regression it exists for.

Suggest: assert that no request overlaps the target day with a width over 2 days, and do not filter those requests out first.

'created without storage state',
timeout: 45_000,
})
.not.toMatch(/localhost:4215/);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The port is hardcoded, but E2E_CONCIERGE_URL can override CONCIERGE_URL. With E2E_CONCIERGE_URL=http://localhost:4300, the URL never matches /localhost:4215/, so .not.toMatch passes at once. This is the main auth-boundary test, and in that case it tests nothing.

Suggest: compare against new URL(CONCIERGE_URL).origin.

await selector.click();
const option = staffPage
.locator('.cdk-overlay-container mat-option')
.filter({ hasText: 'E2E Extra Level' })

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createSecondLevel names each worker's level E2E Extra Level <workerIndex>, but this filter plus .first() matches any of them. If two workers run at the same time, or a crashed run left a level behind (removeSecondLevel ignores errors), the test can click another worker's level. E2E Extra Desk <n> then never appears and toPass times out.

Suggest: match the exact name `E2E Extra Level ${parallelIndex}`.

const body = await res.text();
if (res.ok()) {
const booking = JSON.parse(body) as Booking;
if (await persisted(api, booking.id)) return booking;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

persisted() returns res.ok(), so any non-OK GET counts as "not committed". That includes a 500 from the same poisoned connection this retry works around. If the POST commits and the read-back GET returns 500, the loop POSTs again, gets a 409 for an exclusive desk or parking asset, and throws. The first committed booking was never added to created, so it leaks and blocks that slot on later runs. bookings.api.ts:127 and the other per-area copies have the same logic.

Suggest: retry only on a 404. On other errors, retry the GET, or record the id for cleanup before you throw.

test('shows the active booking beyond the cancelled-history limit', async ({
browser,
}, testInfo) => {
const admin = await apiFor('admin');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apiFor('admin') reads worker 0's admin token file, but this test requests only browser, so nothing makes sure that file exists or is fresh. On a clean CI checkout, if this test runs on worker 2 before worker 0 creates adminStorageState, readToken throws No auth token for admin (worker 0). Locally, an old admin-0.token.json gives an expired bearer and a 401. bookings-empty.spec.ts avoids this because it depends on adminStorageState. desk.zones.ts, room.seed.ts and parking.seed.ts have the same apiFor('admin', 0) risk.

Suggest: depend on the fixture, or mint the admin token in globalSetup.

try {
const page = await context.newPage();
await useSettings(page, ROOM_BASE_SETTINGS);
await page.goto(`${APP_URL}/#/book/meeting/form`);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CON-CAT-03 opens the workplace app at APP_URL, but apps/concierge/playwright.config.ts starts only the concierge dev server. If you run --config apps/concierge/playwright.config.ts --project=local without a separate workplace server, you get ERR_CONNECTION_REFUSED even though the feature works. Only the handover doc mentions this dependency.

Suggest: add workplace as a second webServer, or skip the test when APP_URL is not reachable.

expect(
(await getBooking(adminApi, booking.id)).approver_email,
'and it should record WHO approved it — the concierge, not the holder',
).toBe('support@place.tech');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

support@place.tech is hardcoded, but env.ts lets E2E_ADMIN_EMAIL change the admin identity. With an override, this test (CON-DESK-07), CON-DESK-01 (line 129) and CON-PARK-01 (concierge-parking.spec.ts:114) fail even though the app behaves correctly.

Suggest: use roleFor('admin').email.

…-concierge-2026-09-18

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
@sharmilaseenivasan17

Copy link
Copy Markdown
Author

Revalidated the previously guarded scenarios against the latest develop using Playwright automation.
Result: 9/10 passed ✅

  • DESK-GROUP-04 – Partial-success handling ✅
  • HOME-04 – Upcoming booking displayed ✅
  • HOME-08 – Cancelled booking removed from Upcoming ✅
  • HOME-15 – Home page loads without uncaught exception ✅
  • ROOM-11 – Room cancellation persisted successfully ✅
  • ROOM-13 – Room booking now carries zone hierarchy ✅
  • ROOM-14 – Selected meeting duration stored correctly ✅
  • VIS-15 – Single-visitor invite can be re-saved ✅
  • ROOM-27 – All-day room booking stored correctly with all_day: true ✅
  • ROOM-21 – ❌ Zones are now included, but the booking still returns 403 for a non-admin user when approved: true. Kept as test.fixme.
    The other passing scenarios have had their test.fixme guards removed where applicable.

…e check

Thirteen `test.fixme` guards come off: ROOM-28 and CON-SURV-01 are fixed
on develop, DESK-21 passes as written, VIS-28 and VIS-28b hold on the
2.2609.6 images, and HOME-09, LOCK-01/03/04, ROOM-22, ROOM-24, CON-B2
and ROOM-21 pass once their app fixes are on develop (#544 to #548).

ROOM-21 now asserts what the app can promise: a standard user's booking
under `app.bookings.no_approval` is stored and pending rather than
refused, since staff-api only accepts `approved` from approvers.

`room-booking` asserted the booking's own title is "Room Booking";
develop keeps the meeting name on a native booking, so it asserts that.

The workplace locker seeder used the same asset names as the concierge
seeder on the same building zone, so each found the other's lockers.
Its assets are now `E2E Workplace Locker*`.

ROOM-22 accepts an order linked by `parent_id` as well as by
`extension_data.event_id`.
@vercel

vercel Bot commented Oct 7, 2026

Copy link
Copy Markdown

Deployment failed for project frontend-templates with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/placeos?upgradeToPro=build-rate-limit

@camreeves

Copy link
Copy Markdown
Contributor

I've pushed 0717cf3 onto this branch (fast-forward on top of 14962f6). It takes 14 of the 20 test.fixme guards off, after running everything merged with current develop against the 2.2609.6 release images.

Guards lifted with no app change needed: ROOM-28 (Alex's PPT-2805 fix on develop), DESK-21, VIS-28 and VIS-28b (the pg-orm fix is in the release images now, 4 of 4 runs held), CON-SURV-01 (the survey builder omits the id now).

Guards lifted that need an app fix first. Each of these was hiding a real bug on develop, so there are five PRs against develop, each verified by the test that found it:

Until those five are in develop, the tests they unblock will fail here when run merged with develop, so they should go in first.

Other changes in the commit: room-booking asserted the booking's own title is "Room Booking", and develop now keeps the meeting name on native bookings, so it asserts that. ROOM-22 accepts an order linked by parent_id. The workplace locker seeder used the same asset names as the concierge seeder on the same building zone, so each found the other's lockers and CON-LOCK-01b failed; its assets are now E2E Workplace Locker*.

Result with all of the above applied, fresh stack, CI env: workplace 138 pass, 1 fail, 3 skipped (now 2 with ROOM-21 lifted); concierge 48 pass, 0 fail, 5 skipped, including CON-CAT-03 with the workplace server up. The one workplace failure is YB-12 (bookings-limit), which is intermittent: Your Bookings fetches one 100-row page with deleted rows included, so the active booking is only on page one when the database happens to order it first. Not touched here.

Still guarded, and why:

  • HOME-10: needs a driver reporting room status, and the e2e stack runs no core or drivers
  • VIS-24: the Booking model skips clash checks for visitors on purpose, so this is a product question
  • CON-B1: the access guard admits everyone when no access group is configured, a product call
  • CON-B3: asks for asset_name on a locker booking, and the Booking model has no such column
  • CON-DAY-02..07, CON-REP-02, CON-STAFF-01/02: need a real calendar and directory, or an events.use_bookings switch in concierge

One thing for the CI side: the self-hosted runner never pulls images, so VIS-28 and VIS-28b will fail there if it is still on the July release.

…hub.com/PlaceOS/user-interfaces into test/e2e-workplace-concierge-2026-09-18

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
@camreeves

Copy link
Copy Markdown
Contributor

Merge order for this one, so the lifted tests go green on the nightly rather than red:

  1. ci(e2e): pull the current backend images on every run #549 first. The self-hosted runner has never re-pulled latest, so the nightly has been testing the July release. ci(e2e): pull the current backend images on every run #549 makes the bring-up pull the current images on every run. Without it, VIS-28 and VIS-28b (lifted here because the pg-orm fix is in 2.2609.6) fail on the runner.
  2. Then fix(bookings): let a locker be chosen and booked from the locker form #544 to fix(bookings): leave approval to the backend for users who cannot approve #548, the app fixes behind LOCK-01/03/04, ROOM-22, ROOM-24, HOME-09, ROOM-21 and CON-B2.
  3. Then this PR.

The stack now runs core (develop, e2e/core-in-stack), so a seeded room can
carry the Place::Bookings module the workplace app binds for its status.
room.seed.ts attaches that module and the demo calendar it polls to every
room it creates, waits for the status to be published, and up.sh runs it
at bring-up so no spec waits on a driver. A room therefore reports free
until something is put in its calendar.

HOME-10 (home-availability) loses its guard: the panel lists the seeded
room and its button opens the booking modal, which exercises the driver
process, redis, the rest-api websocket and the app in one assertion.
ROOM-23 (room check-in) binds the same module and is now unwritten rather
than blocked; its row and the desk check-in header say what it needs.
@camreeves

Copy link
Copy Markdown
Contributor

HOME-10 is off the fixme list as well, pushed as 8546c15 on top of your visitor rebooking commit.

What it took: the stack had no core, so nothing in it could ever report a room status. Two PRs sit behind this commit:

On this branch, room.seed.ts now gives every room it creates a Calendar and a Bookings module and waits for the status, and up.sh runs it after seed.ts so no spec ever waits on a driver. A room reports free until something goes into its calendar, so HOME-10 asserts the real chain: driver process, redis, the rest-api websocket, the app. ROOM-23 (room check-in) binds the same module, so its row now says unblocked with no spec yet, and what the spec needs (an event covering now in the room's Calendar_1).

Merge order becomes: drivers#639 first (then I re-pin #550 to master), #549, #550, #544 to #548, then this PR. Until #550 is in develop this commit's import of e2e/support/drivers fails on the branch alone, the same situation as the fix PRs.

Two things the full run surfaced that are in the suite rather than in the app. Both passed on 7 Oct and both come down to timing:

  • parking-favourites (PARK-12), and a flaky PARK-01: the parking seeder runs lazily from the specs and asset names are not unique, so four workers starting together created E2E Parking 1 and E2E Parking 2 twice each, 5 ms apart. PARK-12 then hits a strict mode violation on the duplicate row, 3 of 3. Same shape as the locker seed collision. Seeding parking at bring-up, or re-listing after a create, would fix it.
  • room-recurring ROOM-25b: the booking is anchored to the Sydney date but dayBounds() builds the query window from the process clock, which CI pins to UTC. From 13:00 UTC the Sydney date is a day ahead, the week-0 lookup misses, 3 of 3. The nightly at 01:10 UTC never sees it; an afternoon UTC run does. CON-DESK-08 is the concierge cousin: it books todayAt(18), which is over by 23:00 UTC, so its reject button is disabled.

Numbers on this stack with everything applied, run starting 23:04 UTC: workplace 130 pass / 2 flaky / 2 fail / 6 skipped (the extra skips are the stillToday guards late in the UTC day), concierge 46 / 0 / 2 / 5 without the workplace dev server up.

… tenant

CON-B1 loses its guard: concierge now admits admin and support users
only until app.allow_access_groups is set (develop, #551), so a plain
staff user lands on /unauthorised. The test waits for that bounce or the
shell, whichever comes first.

CON-B3 asserts what can be true: a booking has no asset name column, so
the listing resolves the locker's name from the lockers it has loaded
(develop, #552). CON-LOCK-03 asserts the name too.

The calendar-backed rows run against the Microsoft 365 sandbox tenant
the stack can now be seeded with (develop, e2e/calendar-tenant), and
skip without it. calendar.seed.ts owns the one room with a real mailbox
and the events the specs put on it; the fixtures gain the calendar
identity, an admin whose address is a mailbox there, because staff-api
only lets a user host for themselves. CON-DAY-02/03/04, CON-STAFF-01/02
and CON-REP-02 are written; CON-DAY-05 is not yet, and CON-DAY-06/07
wait on the approvals driver.
@camreeves

Copy link
Copy Markdown
Contributor

The concierge rows are done as far as they go today, pushed as 36232ce on top of my HOME-10 commit. Where each landed:

CON-B1, the access default. Call made: concierge admits admin and support users only until app.allow_access_groups is set. That is #551, done in the shared guard through a default_groups the app provides, so a configured list still replaces it and the other apps are untouched. The row now runs: a plain staff user lands on /unauthorised. If a deployment relied on the open default, it is one setting to put back.

CON-B3, the locker name. There is no column to store it: staff-api's Booking has asset_id and asset_ids, and the asset_name sent on create never comes back (desks look like they keep it because that listing resolves the name from metadata). #552 makes the locker listing do the same from the lockers it has loaded. The row now asserts the screen, not the record, and CON-LOCK-03 asserts the name instead of the id.

The calendar and directory rows, against the Microsoft 365 sandbox tenant, the one placeos-dev and HIO UAT use. #553 lets the stack be seeded with it: with E2E_O365_TENANT, E2E_O365_CLIENT_ID and E2E_O365_CLIENT_SECRET set (CI has them as repository variables and a secret, on the app "PlaceOS Bookings Visualiser"), the tenant row gets real app-only credentials and the seed creates a local admin whose address is a mailbox there, AdeleV@0cbfs.onmicrosoft.com. That last part is forced by staff-api: on an app-only tenant a user can only host an event for themselves, so whoever books through the calendar has to be a real mailbox. Everyone else stays local. Without the variables nothing changes and these rows skip.

On this branch calendar.seed.ts owns E2E Calendar Room, whose address is testroom4@0cbfs.onmicrosoft.com (shared with placeos-dev's Sydney Room 4; the sandbox has no spare room mailboxes, so the specs book days out and delete what they make), and the fixtures gain calendarApi and calendarPage for that identity. Rows running against it, all green locally:

  • CON-DAY-02/04: a booking the calendar identity makes through the API appears on the admin's day view. Different user viewing from the one who booked, which is the point of the row.
  • CON-DAY-03: the next day does not show it, the day before does again.
  • CON-STAFF-01: the directory lists the tenant's users and search narrows it.
  • CON-STAFF-02: "opening a user" on this page is the check-in control on their row, so the row stores a staff booking and check-out ends it. One thing to know: check-out stamps the end with the current second and staff-api refuses an end equal to the start, so the test pauses a second between the two clicks. A person cannot click that fast; Playwright can.
  • CON-REP-02: the rooms report total for a day and the building equals the API's event count for the same window.

Still guarded: CON-DAY-05 (booking from the day view's modal; not mapped yet, and it has to run as the calendar identity) and CON-DAY-06/07 (the approvals panel renders only with an approvals driver binding, which the stack does not run).

Two things worth knowing from the runs: GET /api/staff/v1/events?zone_ids= answers 206 on this stack because the native rooms' @place.tech addresses are not mailboxes (staff-api names them in X-Calendar-Issue and the app treats it as success); and DELETE /events/:id needs ?system_id= with the id from the room's own listing, since Exchange gives each mailbox its own id for the same event.

Merge order now: drivers#639, then #549, #550, #553 (each stacked on the one before), then #544 to #548, #551 and #552 against develop, then this PR. The tenant secret expires 2027-10-07; it is on the pending list to re-mint.

#551 was closed: with no access group configured concierge admits every
signed-in user, and that default stays because existing deployments rely on
it. CON-B1 asserted the opposite and would have failed on every run.

The row now asserts the decided behaviour: a plain staff user is given the
shell, is not sent to /unauthorised, and is still not offered the management
pages, which the sidebar withholds on its own is_admin check. CON-AUTH-02
remains the test of the guard with a group set. The file header no longer
describes the default as a hole.
@camreeves

camreeves commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

CON-B1 has flipped, pushed as 095a72a. Alex closed #551: the open default is the expected behaviour, and changing it would break existing clients.

So the row now asserts that behaviour instead of a refusal. With no access group configured, a plain staff user is given the concierge shell, is not bounced to /unauthorised, and is still not offered the management pages. That last part is the sidebar's own is_admin check, which reads the user's flags and groups rather than the access setting, so the row still shows the admin surfaces are withheld. CON-AUTH-02 is unchanged and remains the test of the guard itself, with a group set.

The file header no longer describes the default as a hole. Access spec 4 of 4 on the stack with #551 removed.

Merge order is now: drivers#639 (then re-pin #550), #549, #550, #553, then #545 to #548, then this PR. #544 and #552 are in.

@MrYuion MrYuion changed the title Test/e2e workplace concierge 2026 09 18 test(e2e): workplace and concierge [2026-09-18] Oct 8, 2026
#549 moved the Microsoft 365 variables to the bring-up step and added
`calendarBacked(api)`, which reads the tenant row the seed wrote. The test
step no longer sees the variables, so a skip keyed on them would skip the
calendar rows on a stack that is backed.

The concierge fixtures gain a worker fixture, `conciergeCalendarBacked`,
answered once per worker with the admin token, and the calendar identity's
state and token fixtures key off it. The three tenant-only groups skip from
a `beforeEach` on the same fixture.
@camreeves

Copy link
Copy Markdown
Contributor

One more commit, 7c4d7e5: the calendar rows now ask the stack whether it is backed, the way #549 does it. Alex moved the Microsoft 365 variables to the bring-up step and added calendarBacked(api), so a skip keyed on the variables would have skipped the rows in CI on a stack that is backed. The concierge fixtures gain a worker fixture conciergeCalendarBacked, answered once per worker with the admin token, and the three tenant-only groups skip from a beforeEach on it. Checked on a stack seeded without the tenant: CON-STAFF-01/02 skip, CON-STAFF-00 and CON-B2 run, and the e2e typecheck is clean with #549's support code.

With #549 in develop this branch should now resolve everything it imports.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants