-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathauth.spec.ts
More file actions
173 lines (133 loc) · 6.41 KB
/
Copy pathauth.spec.ts
File metadata and controls
173 lines (133 loc) · 6.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* E2E tests for authentication flow.
*
* @remarks
* Tests ATProto OAuth login flow, session handling, and protected routes.
* Uses the industry-standard ATProto OAuth pattern.
*
* @see {@link https://atproto.com/specs/oauth} - ATProto OAuth Specification
* @see {@link https://docs.bsky.app/docs/advanced-guides/oauth-client} - OAuth Client Guide
*/
import { test, expect } from '@playwright/test';
import { SignInPage } from './fixtures/page-objects.js';
test.describe('Authentication', () => {
test('displays login page', async ({ page }) => {
const signInPage = new SignInPage(page);
await signInPage.goto();
await expect(signInPage.handleInput).toBeVisible();
await expect(signInPage.continueButton).toBeVisible();
});
test('login page has proper heading', async ({ page }) => {
const signInPage = new SignInPage(page);
await signInPage.goto();
const heading = page.getByRole('heading', { level: 1 });
await expect(heading).toContainText(/sign in/i);
});
test('form input accepts text', async ({ page }) => {
const signInPage = new SignInPage(page);
await signInPage.goto();
// Enter a handle
await signInPage.enterHandle('user.bsky.social');
// Verify the input is filled correctly
await expect(signInPage.handleInput).toHaveValue('user.bsky.social');
});
test('form submits with valid handle format', async ({ page }) => {
const signInPage = new SignInPage(page);
await signInPage.goto();
await expect(signInPage.handleInput).toBeVisible();
await signInPage.enterHandle('user.bsky.social');
// Verify value is set
await expect(signInPage.handleInput).toHaveValue('user.bsky.social');
await signInPage.continueButton.click();
// Form submission should show loading state or navigate/show error
// In test environment without configured OAuth, we expect either:
// 1. Button shows loading state (Connecting...)
// 2. An error appears (OAuth client not configured)
// 3. URL changes (redirect to OAuth provider)
const buttonOrError = page
.getByRole('button', { name: /connecting/i })
.or(page.getByRole('alert'))
.or(page.locator('body'));
// Wait for something to happen (form was processed)
await expect(buttonOrError).toBeVisible({ timeout: 10000 });
});
test('form validation prevents submission of invalid handle', async ({ page }) => {
const signInPage = new SignInPage(page);
await signInPage.goto();
await expect(signInPage.handleInput).toBeVisible();
// Submit without entering anything; form should not navigate away.
await signInPage.continueButton.click();
// Should remain on login page (form didn't submit due to validation)
await expect(page).toHaveURL(/\/login/);
// Form should still be visible (not loading/navigating)
await expect(signInPage.continueButton).toBeVisible();
await expect(signInPage.continueButton).toBeEnabled();
});
test('DID format is accepted in input', async ({ page }) => {
const signInPage = new SignInPage(page);
await signInPage.goto();
// Enter a valid DID format
await signInPage.enterHandle('did:plc:abc123');
// Verify the input is filled correctly
await expect(signInPage.handleInput).toHaveValue('did:plc:abc123');
// Button should still be enabled
await expect(signInPage.continueButton).toBeEnabled();
});
test('header shows sign-in button when logged out', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded' });
// Wait for React hydration (AuthButton is a client component)
const signInButton = page.getByRole('button', { name: /sign in/i });
await expect(signInButton).toBeVisible({ timeout: 15000 });
});
test('protected routes show auth guard or redirect', async ({ page }) => {
await page.goto('/dashboard', { waitUntil: 'domcontentloaded' });
// AuthGuard MUST redirect unauthenticated users to login with redirect param
await expect(page).toHaveURL(/\/login\?redirect=.*dashboard/, { timeout: 10000 });
});
test('submit page requires authentication', async ({ page }) => {
await page.goto('/submit', { waitUntil: 'domcontentloaded' });
// Submit page shows sign-in form for unauthenticated users
const signInHeading = page.getByRole('heading', { name: /sign in to chive/i });
await expect(signInHeading).toBeVisible({ timeout: 10000 });
// Should show sign-in button and redirect indicator
const signInButton = page.getByRole('button', { name: /continue with at protocol/i });
await expect(signInButton).toBeVisible();
// Should indicate redirect to /submit after signing in
const redirectIndicator = page.getByText(/redirected to \/submit/i);
await expect(redirectIndicator).toBeVisible();
});
test('login page shows ATProto description', async ({ page }) => {
const signInPage = new SignInPage(page);
await signInPage.goto();
// Should mention Personal Data Server in the description
const description = page.getByText(/personal data server/i).first();
await expect(description).toBeVisible();
});
test('login preserves redirect URL', async ({ page }) => {
// Try to access protected page
await page.goto('/dashboard', { waitUntil: 'domcontentloaded' });
// Should redirect to login with redirect parameter
await expect(page).toHaveURL(/redirect=/, { timeout: 10000 });
});
test('displays terms and privacy links', async ({ page }) => {
const signInPage = new SignInPage(page);
await signInPage.goto();
await expect(signInPage.termsLink).toBeVisible();
await expect(signInPage.privacyLink).toBeVisible();
});
test('login form is accessible', async ({ page }) => {
const signInPage = new SignInPage(page);
await signInPage.goto();
// Assert the association, not merely that the words appear somewhere:
// `getByLabel` only matches when the label is actually bound to the
// control, which is the property a screen reader depends on. The old
// `getByText('Handle or DID')` matched the label and the description
// both, and would have passed even while the input had no accessible
// name at all — which is exactly the state the page was in.
await expect(page.getByLabel('Handle or DID')).toBeVisible();
await expect(signInPage.handleInput).toBeVisible();
// Button should be focusable
await signInPage.continueButton.focus();
await expect(signInPage.continueButton).toBeFocused();
});
});