Skip to content

test(e2e): cover room bookings, Your Bookings, the desk gaps and parking - #493

Merged
sharmilaseenivasan17 merged 4 commits into
automated-testingfrom
test/e2e-workplace-coverage
Sep 16, 2026
Merged

sharmilaseenivasan17 merged 4 commits into
automated-testingfrom
test/e2e-workplace-coverage

Conversation

@sharmilaseenivasan17

@sharmilaseenivasan17 sharmilaseenivasan17 commented Sep 16, 2026

Copy link
Copy Markdown

Stacked on #492 (visitor invites), so this diff is only the new work. Base is
automated-testing; review #492 first if it has not been read yet.

61 tests in this PR. 55 passing, 6 blocked on app defects, 0 failing.
Every test was run against the local stack; the parallel-run numbers are below.

Area Tests Passing Blocked Before this PR
Room bookings 22 17 5 nothing — the row said out of scope (external)
Your Bookings 14 14 0 nothing of its own
Desk bookings 10 10 0 4 tests (the originals, untouched and not in this diff)
Parking 15 14 1 nothing at all
Total 61 55 6

Coverage rows are in E2E_USER_STORIES.md (§1b for rooms, ROOM-01 … ROOM-23),
and e2e/ROOM_E2E_HANDOVER.md is the working handover for the room area.

What this changes about WP-E2E-15

Rooms were marked out of scope (external), and that was only half right. With
app.events.use_bookings = true the meeting form saves an ordinary PlaceOS
booking of type room and reads availability from the bookings list — no
outbound call. That row is now partial, split along the honest line: the
native path is covered, the calendar path still needs a real Microsoft/Google
tenant and stays opt-in.

Please say this out loud in review: a green room run proves the
PlaceOS-native path works and says nothing about the calendar path. Whether
customers book rooms through the calendar or through use_bookings decides what
this coverage is worth, and nobody has answered it yet.

Findings — five, none filed

Reproduced and minimised; each has a fixme test waiting on it.

ID What happens
ROOM-B5 Catering cannot be ordered with a native room booking. The meeting saves (201) and the order is refused 422 error linking booking to event — orders link to a calendar event by id, and here the id is a booking id. The room booking is left behind undeleted and no order exists, while the user is shown an error and reasonably believes nothing was booked.
ROOM-B4 Cancelling a room booking from the schedule fires DELETE /events/<id> → 500. The room stays held.
ROOM-B3 The meeting length picked is not the length booked: ask for 90, the room is held for 60, and the confirmation showed 90.
ROOM-B2 A room booked through the app is stored with zones: [], unlike desk and visitor bookings.
PARK-B1 With parking.require_space_restriction on, the parking booking form cannot be submitted at all: the shared validator requires a space_restrictions field that parking-form-details.component.ts never renders, so Confirm answers Some fields are invalid. [space_restrictions] naming a field nobody can fill. The setting's own schema describes it as belonging to the parking request flow, which does render one.

ROOM-B1 (a non-admin sending approved: true without zones gets a 500 Postgres
syntax error) was re-measured through the app and still reproduces.

Two things worth knowing beyond the findings

REG-09 is worse than its row says. POST /bookings returned 201 with an
id
for a booking that then 404s — the row was never committed, because the
response is written before the COMMIT that fails. It presents as every
downstream symptom except the real one; it cost three "the card isn't there"
investigations. Every API create in the new support code now reads the row back
and retries, and that workaround is marked for deletion along with the rest of
the REG-09 handling.

The schedule renders cancelled bookings for ever. It requests bookings with
include_deleted: true and only hides what the user cancelled in the current
session, so the number of cards on a day grows with every run — measured at
eight cards on a day where the backend reported zero live bookings. This is the
same mechanism as the 100-row listing problem, seen from the app side. No test
here asserts a card count as a result; they compare against what the backend
says is live.

Blocked by the environment rather than by a bug

  • ROOM-23, checking in to a room: needs a live Bookings driver module on
    the room's System. This stack has one driver (spec_helper) and one module
    (PrivateHelper), so the control can never render. No spec, deliberately.
  • DESK-14 is covered through the PlaceOS user list only:
    app.basic_user_search switches the host picker away from
    /api/staff/v1/people, the calendar directory, which 500s here.

Structure

One folder per page, sharing nothing across pages:

e2e/support/room/      rooms + the catering menu seeder
e2e/support/bookings/  the Your Bookings page
e2e/support/desk/      the desk gaps (the dev's shared files are untouched)
e2e/support/parking/   parking, including its seeder

The schedule page object still belongs to visitor/your-bookings.page.ts, which
introduced it; the other three areas inherit it rather than copying, and the
form that "Edit" lands on is now an overridable hook because on the Your
Bookings page it depends on the booking rather than the page. The only edits to
existing files are that hook and one privateprotected.

Seeding is per area and never in the shared seed.ts: rooms are engine Systems
(three per worker), the catering menu is assets, and parking needs a level zone
tagged parking plus space assets. All need admin, all are idempotent, all are
cached per process.

Running it

e2e/stack/up.sh
export E2E_BACKEND_URL=https://localhost:9443
bun run e2e:typecheck
bunx playwright test --config apps/workplace/playwright.config.ts

Latest runs: rooms 18 passed / 5 skipped twice back to back; Your Bookings +
desks 29 passed in one parallel run; parking 15 passed / 1 skipped. Four
tests self-skip on a single worker because they need a genuinely different
second user — run with the default worker count.

Not done, and deliberately not in this PR

  • The home page and the remaining room/visitor gaps are planned and not
    yet written.
  • Nothing should go near CI until stale bookings are settled. Cancelled
    bookings accumulate per user and the schedule counts them against a 100-row
    limit; past that, card-based specs fail for reasons unrelated to the app. It
    is a backend change or a purge step, not a spec change.
  • This branch is named test/... on purpose: a branch under e2e/** would fire
    the advisory workflow on the single self-hosted runner, which is exactly what
    should not happen until the above is settled.

🤖 Generated with Claude Code

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>
@vercel

vercel Bot commented Sep 16, 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 Sep 16, 2026 8:26am UTC

@sharmilaseenivasan17
sharmilaseenivasan17 merged commit b2dfe8d into automated-testing Sep 16, 2026
2 checks passed
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.

1 participant