Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,11 @@ dist
*.sw?

.cursor
.claude
.claude

# Playwright artifacts
test-results/
playwright-report/
blob-report/
playwright/.cache/
e2e/.storage/
73 changes: 73 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Auth dapp E2E tests

Playwright tests organised into three projects.

## Projects

| Project | Specs | Target | When to run |
|---|---|---|---|
| `local` (default) | everything except `request-types-*` and `magic-google-real-oauth` | local Vite preview on `localhost:5174` (CI=true) or `npm start` (dev) | every PR |
| `real-auth` | `request-types-*.spec.ts` | dev deployment `https://decentraland.zone/auth` + live `auth-api.decentraland.zone` | every PR |
| `real-oauth` | `magic-google-real-oauth.spec.ts` | dev deployment + Google login | nightly |

## Running

```bash
# Default suite (mock-driven, fast)
CI=true npx playwright test --project=local

# Real auth-server (no /v2/requests/** mocking — creates real requests)
SKIP_WEBSERVER=1 npx playwright test --project=real-auth

# Nightly: real OAuth round-trip through Google
SKIP_WEBSERVER=1 E2E_GOOGLE_EMAIL=... E2E_GOOGLE_PASSWORD=... npx playwright test --project=real-oauth
```

`SKIP_WEBSERVER=1` disables Playwright's local Vite webServer for projects that
target the deployed dapp at `decentraland.zone/auth`.

## Required env vars (real-oauth project)

| Var | Purpose | Source |
|---|---|---|
| `E2E_GOOGLE_EMAIL` | Test Google account email | dedicated test account; never a real employee account |
| `E2E_GOOGLE_PASSWORD` | Test Google account password | same |
| `E2E_PERSIST_STORAGE_STATE` (optional) | When set, writes `e2e/.storage/google-session.json` after first successful login | CI job that persists artifacts |

Missing env vars cause the spec to auto-skip (`test.skip`), so it's safe to run
the suite without configuring secrets.

### Provisioning the test Google account

1. Create a dedicated Google account (do NOT reuse a real one).
2. In the Magic dashboard, ensure the staging publishable key has
`https://decentraland.zone/auth/callback` in its allowed redirect URIs.
3. Add the email/password to CI secrets:
```
gh secret set E2E_GOOGLE_EMAIL --body "<email>"
gh secret set E2E_GOOGLE_PASSWORD --body "<password>"
```

## Known caveats

- **CAPTCHA**: Google occasionally challenges headless Playwright sessions with
reCAPTCHA. The first run after long inactivity is the riskiest. Wire as a
nightly job and don't gate PRs on it.
- **`storageState` reuse**: Persist `e2e/.storage/google-session.json` between
runs to skip Google's email/password steps and avoid bot detection.
- **2FA**: The test Google account must NOT have 2FA enabled or the password
step will hang.

## Helpers

- `injectMockWallet(context)` — mock Ethereum provider (legacy, fixed `MOCK_WALLET`).
- `injectFullSession(context)` — fresh wallet keypair + real AuthIdentity in
localStorage; the same identity can be used to sign auth-server payloads.
- `mockApiRoutes(page, options)` — auth-server, profile, feature-flag, segment
mocks. Skip its `/v2/requests/**` routes via `page.route('**/v2/requests/**',
route => route.continue())` to mix mocked profile with real auth-server.
- `createAuthServerRequest({ method, params, identity? })` — POST to the real
auth-server (`auth-api.decentraland.zone/requests`) to obtain a `requestId`
for `eth_sendTransaction` (auth-chain required) or `dcl_personal_sign`.
- `mockCatalystDeployRoute`, `mockNewsletterRoute`, `mockReferralRoute` —
setup-page deploy chain.
10 changes: 10 additions & 0 deletions e2e/fixtures/ethereum-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ export function createMockProviderScript(walletAddress: string): string {
return '0xDE0B6B3A7640000'; // 1 ETH
case 'eth_estimateGas':
return '0x5208';
case 'eth_sendTransaction':
case 'eth_sendRawTransaction': {
// Journal the submission so tests can assert that the dapp
// forwarded the same tx params it received from the auth-server,
// without ever broadcasting. Returns a deterministic fake hash.
if (typeof window.__e2eRecordTx === 'function') {
try { await window.__e2eRecordTx(method, params); } catch (_) { /* non-fatal */ }
}
return '0x' + 'ab'.repeat(32);
}
default:
return null;
}
Expand Down
29 changes: 29 additions & 0 deletions e2e/fixtures/mock-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,35 @@ export const emptyProfileResponse = {
avatars: []
}

/**
* Active-entities response for a `default<N>` profile pointer.
* Used by `SetupPage`'s `deployProfileFromDefault` flow when it calls
* `client.fetchEntitiesByPointers(['defaultN'])` to seed the deployed entity.
*/
export const defaultProfileEntityResponse = {
id: 'default-entity',
type: 'profile',
pointers: ['default1'],
timestamp: Date.now(),
content: [],
metadata: {
avatars: [
{
name: 'default1',
description: '',
avatar: {
bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',
eyes: { color: { r: 0.125, g: 0.703, b: 0.964 } },
hair: { color: { r: 0.234, g: 0.128, b: 0.065 } },
skin: { color: { r: 0.8, g: 0.608, b: 0.465 } },
wearables: [],
snapshots: {}
}
}
]
}
}

/** Profile: returns existing user */
export const existingProfileResponse = {
avatars: [
Expand Down
Loading
Loading