Skip to content

test(ui): cover the open auth, members and API key e2e cases - #7272

Merged
otavio merged 6 commits into
masterfrom
test/ui/e2e-open-cases
Oct 1, 2026
Merged

otavio merged 6 commits into
masterfrom
test/ui/e2e-open-cases

Conversation

@luizhf42

Copy link
Copy Markdown
Member

Summary

Adds the 11 open Playwright cases from shellhub-io/team#243 for Authentication, Members & Invitations and API Keys. Enterprise and cloud accept devices and open the admin panel only under a license, so the e2e stack now mounts one on those editions.

e2e/
├── auth.spec.ts       + MFA sign-in reaches the code prompt (enterprise, cloud)
│                      + a member removed on the server is logged out on the next request
├── members.spec.ts    + signing up through an invitation consumes it
│                      + a non-admin's invitee signs in only after an instance admin approves (enterprise)
│                      + paired devices: accept, demote, remove, leave, delete account (cloud)
├── api-keys.spec.ts   + namespace and instance keys show their plaintext only at creation
├── helpers.ts         ~ createTeam, createTeamWithMember, signInAndOpen, signUpFromInvite now shared
└── seed.ts            ~ createUser({ admin }), enableMFA, readUserInvitationStatus
Up(edition)
  if edition != community
    SHELLHUB_LICENSE_FILE from the shell, else .env.override
    fail if unset or the file is missing
    mount it into server as ADMIN_API_LICENSE_FILE

The license covers seven tests: the five paired-device cases (the server refuses to accept a device without one) and the approval and instance key cases (every /admin/* page redirects to /admin/license without one). CI writes the TEST_LICENSE secret to a file for the enterprise and cloud jobs.

InfoItem now links each value to its label with aria-labelledby, so screen readers announce the label and the "Paired by" check can find it with getByLabel.

Evidence

  • Before: the 11 cases had no Playwright coverage. On enterprise and cloud, a device accept answered 402 "device license limit reached", and /admin/users and /admin/instance-api-keys redirected to /admin/license.
    After: fresh stacks on each edition:
    community    21 passed, 9 skipped
    enterprise   21 passed, 9 skipped
    cloud        29 passed, 1 skipped
    
    Broken on purpose to confirm they fail: unticking the keep checkbox (demote and remove), skipping the server-side removal (session test), and not enabling MFA (MFA test). stack up with a missing license path fails before building anything.

Merge Danger

Door: two-way

Blast Radius: CI

Enterprise and cloud e2e jobs fail at the license step until the TEST_LICENSE secret is set on the repo. Fork pull requests skip those jobs, so they're unaffected. The one production change is the aria-labelledby on InfoItem, which only adds an accessible name.

@luizhf42
luizhf42 requested review from a team as code owners September 30, 2026 18:37
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Claude finished @luizhf42's task in 5m 40s —— View job


Code Review Complete

Reviewed 14 files. Posted 1 inline issue. Standards: 0 violations, 1 judgement call. Spec: not run. Correctness: 0.

Standards

No documented rule is broken. The one judgement call is a Mysterious Name: licenseFile in tests/environment/stack.go:76-78 holds an env map, not a file path.

Spec

The Spec axis did not run. The PR references shellhub-io/team#243, but that repo isn't readable from this workflow, so there was no spec text to check against.

Additional notes

  • Correctness: I checked these and found nothing wrong:
    • ${SHELLHUB_LICENSE_FILE} in the compose file: docker-compose.enterprise.test.yml is loaded only for enterprise and cloud stacks (tests/environment/edition.go), and the license layer is merged after .env.enterprise's empty value, so it wins.
    • CI secrets: fork PRs skip both the license step and the e2e step, and the secret passes through env: and is never echoed.
    • The stripe.go → shellOrOverride refactor keeps the same behaviour.
    • InfoItem ids come from useId, and the <dt> exists on every render path.
    • The new Playwright tests each seed their own users and namespaces, so they are safe under parallel workers.
  • I dropped 4 judgement calls below the bar, including a proposed duplicate between auth.spec.ts and members.spec.ts. Validation didn't hold that one up: the two invite from admin-owned and non-admin-owned namespaces, and those behave differently on enterprise. No cap was hit.

If you've addressed the feedback and want a new review, tag @shellhub-io/admin and a team member can trigger it.

Comment thread tests/environment/stack.go Outdated
@luizhf42
luizhf42 force-pushed the test/ui/e2e-open-cases branch 3 times, most recently from 2492843 to 19630cf Compare September 30, 2026 19:15
@gustavosbarreto

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Claude finished @gustavosbarreto's task in 5m 39s —— View job


Code Review Complete

Reviewed 14 files. Posted 1 inline issue. Standards: 1 violation, 0 judgement calls. Spec: not run. Correctness: 0.

Standards

ui/apps/console/e2e/api-keys.spec.ts:28-30: generateKey reads the key with the CSS locator generate.locator("code"). Both generate modals already label the key card with aria-labelledby, so react-conventions' "Role first" rule calls for getByLabel.

Spec

The Spec axis did not run. The PR references shellhub-io/team#243, and that repo can't be read from this workflow, so there was no spec text to check against.

Additional notes

  • Correctness: I checked these and found nothing wrong:
    • Fork PRs: they never reach the license step.
    • Compose file: docker-compose.enterprise.test.yml is loaded only for non-community editions, so an unset ${SHELLHUB_LICENSE_FILE} never reaches compose.
    • Env layering: the mergeEnvs order lets the license win over .env.enterprise, and cfg.Envs can still override it on purpose.
    • shellOrOverride: it keeps stripe.go's old behaviour.
    • Relative license paths: they resolve against the repo root, and license_test.go covers this.
    • Secret handling: the secret passes through env: and is never echoed.
    • InfoItem: each instance gets its own useId.
    • UI strings and seed columns: every one the specs rely on exists.
    • Cross-repo: no pkg/ or server/api/services symbols changed.
  • I dropped 1 judgement call in validation: the new password import in helpers.ts is shadowed by existing parameters. Those parameters predate this PR, and the same shape is used across the e2e suite. The earlier licenseFile naming comment was fixed, so I didn't raise it again. No cap was hit.

If you've addressed the feedback and want a new review, tag @shellhub-io/admin and a team member can trigger it.

Comment thread ui/apps/console/e2e/api-keys.spec.ts Outdated
@luizhf42
luizhf42 force-pushed the test/ui/e2e-open-cases branch from 19630cf to 2a13edd Compare October 1, 2026 12:11
@luizhf42
luizhf42 added this pull request to stack #7284 October 1, 2026 15:39
@luizhf42
luizhf42 force-pushed the test/ui/e2e-open-cases branch 2 times, most recently from aef7f7a to d7c9704 Compare October 1, 2026 17:56
@otavio

otavio commented Oct 1, 2026

Copy link
Copy Markdown
Member

/review

@otavio
otavio force-pushed the test/ui/e2e-open-cases branch from d7c9704 to dac4879 Compare October 1, 2026 18:18
@otavio

otavio commented Oct 1, 2026

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Claude finished @otavio's task in 4m 38s —— View job


Code Review Complete

Reviewed 14 files. Posted 1 inline issue. Standards: 1 violation, 0 judgement calls. Spec: not run. Correctness: 0.

Standards

ui/apps/console/e2e/members.spec.ts:433 finds the "Paired by" value with getByLabel. This PR names the InfoItem <dd> through aria-labelledby, and a <dd> has the implicit role definition, so react-conventions' "Role first" rule calls for getByRole("definition", { name: "Paired by", exact: true }). The same issue is in the new InfoItem unit test at InfoItem.test.tsx:22.

Spec

The Spec axis did not run. The PR references shellhub-io/team#243, and that repo can't be read from this workflow, so there was no spec text to check against.

Additional notes

  • Correctness: I checked these and found nothing wrong:
    • License path: it resolves against the repo root as Up's doc comment says, and CI passes an absolute path.
    • Unset compose variable: .env.enterprise's empty SHELLHUB_LICENSE_FILE never reaches compose.
    • psql seed helpers: they quote their values with :'var'.
    • Response waits: the MFA and session tests register waitForResponse before the action that sends the request.
    • Paired-device assertions: each test uses a fresh tenant, so toStrictEqual doesn't depend on other devices or their order.
    • Cross-repo: no pkg/ or server/api/services symbols changed.
  • I didn't raise the earlier licenseFile naming and locator("code") findings again. Both are fixed. No cap was hit.

If you've addressed the feedback and want a new review, tag @shellhub-io/admin and a team member can trigger it.

Comment thread ui/apps/console/e2e/members.spec.ts
@luizhf42
luizhf42 force-pushed the test/ui/e2e-open-cases branch 2 times, most recently from d73f023 to a005d61 Compare October 1, 2026 20:00
Enterprise and cloud accept devices and open the admin users and instance API key pages only
under a license. Without one, accepting a device answers 402 "device license limit reached" and
the admin routes redirect to /admin/license, so the e2e cases for paired devices and instance
keys can't run.

Up now requires SHELLHUB_LICENSE_FILE for both editions and fails before building anything when
it is unset or the file is missing, the same way the cloud stack fails without the Stripe keys.
The value comes from the shell first, then from .env.override, through the lookup the Stripe keys
already used. A relative path resolves against the repo root, where .env.override lives, because
the stack runs from tests/ and the e2e script from ui/, so the working directory means nothing to
whoever set it. The path is made absolute because Compose would resolve a relative bind mount
against the compose file's directory.

CI writes the TEST_LICENSE secret to a file with printf, which adds no trailing newline, and fails
when the secret is empty: an empty file would pass the existence check and only fail later, inside
the server. Fork pull requests never reach this step, because the path filter only runs the
licensed editions for pull requests from this repository.
A detail panel rendered its label as a dt and its value as a dd with nothing linking the two, so
assistive tech read the value without its label and a test could only reach it through the DOM
structure. The dd now points at its dt with aria-labelledby.

Playwright's getByRole never names a definition, even with aria-labelledby set, so tests reach the
value with getByLabel, which follows the same attribute.
Adds the open Members & Invitations cases from team#243: signing up through an invitation consumes
it, a non-admin's invitee waits for an instance admin's approval on enterprise, and a member's
paired devices follow the member through pairing, demotion, removal, leaving and account deletion.

The invitation status is read from user_invitations because no endpoint returns it once the
invitee has an account. The approval case runs only on enterprise, the one edition that holds a
non-admin's invitee for approval. It also carries the sign-up checks for enterprise, which is why
the plain sign-up case skips that edition with the same reason. The instance admin comes from the
server CLI's --admin flag; the first user an instance creates is an admin too, which is why the
existing sign-up test, invited by the seeded admin, never hit approval.

Devices pair over the API, so no agent runs. Each pairing request sends a fresh RSA key, because
the server hands back the code it already issued for a key it has seen, and a random MAC whose
first byte is 0x02, a locally administered unicast address that can't match a real device. The
"Keep <name> as a team device" checkboxes are visually hidden inputs under a styled span that
intercepts clicks, so the tests tick them with the Space key, as the role radios already were.

"Paired by" is read with getByLabel, not getByRole("definition", { name }). Playwright follows
ARIA 1.3, where the definition role a <dd> carries prohibits a name, so the role query never
matches in a browser even though jsdom's Testing Library computes one.

createTeam, createTeamWithMember, signInAndOpen and signUpFromInvite move to helpers.ts because
the auth and API key specs need them too. The 15s waits on the sign-up headings are the ones the
existing sign-up test already used.
Adds the open Authentication cases from team#243.

MFA is turned on through the database, since no API or CLI enables it without a live TOTP code from
the setup flow. The secret is a fixed, public base32 test value: the test stops at the code prompt
and never computes a code. It checks the 401 and the MFA token header on the login response, which
is what sends the console to /mfa-login.

The session case removes the member through the API, then waits for the 401 from /api/devices
before checking the redirect to /login, so the logout is tied to the navigation. The member signs in
without signInAndOpen: its extra page load fires requests that race the removal and log the member
out before the Devices click.
Adds the open API Keys cases from team#243 for namespace and instance keys. Each test reads the key
from the dialog that generates it, proves the key authenticates, then checks that neither the page
nor the list endpoint returns it. The instance key runs on enterprise and cloud, where the admin
panel lives.
@otavio
otavio force-pushed the test/ui/e2e-open-cases branch from e8a9145 to 7f5efa7 Compare October 1, 2026 21:49
… refused

The pairing test now reads the "Accepting as" section before accepting, so it fails if the page
names someone other than the signed-in member. The issue asks for the owner to be shown on the
accept page, and the test only checked the success message and device details.

The demote, remove, leave and account-deletion tests now authenticate as each paired device's
agent after the member is gone, with the same hostname, identity, public key and tenant the
pairing used. A device kept for the team still authenticates (200). A removed one gets 401: the
server refuses a removed device that was enrolled by pairing until it pairs again (authDevice in
server/api/services/auth.go), rather than reviving it as pending. This is the agent's side of
these cases, which team#243 marks as UI plus agent and which the tests only covered from the
API's device list.
@otavio
otavio force-pushed the test/ui/e2e-open-cases branch from 7f5efa7 to a359114 Compare October 1, 2026 22:09
@otavio

otavio commented Oct 1, 2026

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Claude finished @otavio's task in 3m 14s —— View job


Code Review Complete

Reviewed 14 files on two axes: Standards (repo rules, Go and React conventions, tests, code smells) and Correctness (security and runtime defects). No new issues found, so the code can stay as it is. The Spec axis didn't run because the PR references shellhub-io/team#243, and this workflow can't read that repo.

Earlier rounds raised three issues: the licenseFile naming, locator("code"), and "Paired by". All three are resolved. "Paired by" was deliberately put back to getByLabel, because ARIA 1.3 doesn't allow the definition role to have a name, so I'm not raising it again. I dropped four low-severity judgement calls because none of them was something a maintainer would plausibly change:

  • enableMFA writing to the database. Commit 63ba685 explains why.
  • A missing Fixes: trailer. I couldn't confirm the PR closes all of team#243.
  • requestPairing returning both name and body.
  • No :? guard on the compose license variable. Only Up, which already checks it, loads that file.

If you push additional changes and want a new review, tag @shellhub-io/admin and a team member can trigger it.

@otavio
otavio merged commit 4ab553e into master Oct 1, 2026
46 checks passed
@otavio
otavio deleted the test/ui/e2e-open-cases branch October 1, 2026 23:03
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