forked from gordon-code/github-tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsmoke.spec.ts
More file actions
179 lines (157 loc) · 6.34 KB
/
smoke.spec.ts
File metadata and controls
179 lines (157 loc) · 6.34 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
174
175
176
177
178
179
import { test, expect, type Page } from "@playwright/test";
/**
* Register API route interceptors and inject auth + config into localStorage BEFORE navigation.
* OAuth App uses permanent tokens stored in localStorage — no refresh endpoint needed.
* The app calls validateToken() on load, which GETs /user to verify the token.
*/
async function setupAuth(page: Page) {
// Intercept /user validation (called by validateToken on page load)
await page.route("https://api.github.com/user", (route) =>
route.fulfill({
status: 200,
json: {
login: "testuser",
name: "Test User",
avatar_url: "https://github.com/testuser.png",
},
})
);
await page.route(
"https://api.github.com/repos/*/actions/runs*",
(route) =>
route.fulfill({
status: 200,
json: { total_count: 0, workflow_runs: [] },
})
);
await page.route("https://api.github.com/notifications*", (route) =>
route.fulfill({ status: 200, json: [] })
);
await page.route("https://api.github.com/graphql", (route) =>
route.fulfill({
status: 200,
json: {
data: {
search: { issueCount: 0, pageInfo: { hasNextPage: false, endCursor: null }, nodes: [] },
rateLimit: { remaining: 5000, resetAt: new Date(Date.now() + 3600000).toISOString() },
},
},
})
);
// Seed localStorage with auth token and config before the page loads
await page.addInitScript(() => {
localStorage.setItem("github-tracker:auth-token", "ghu_fake");
localStorage.setItem(
"github-tracker:config",
JSON.stringify({
selectedOrgs: ["testorg"],
selectedRepos: [{ owner: "testorg", name: "testrepo", fullName: "testorg/testrepo" }],
onboardingComplete: true,
})
);
});
}
// ── Login page ───────────────────────────────────────────────────────────────
test("login page renders sign in button", async ({ page }) => {
await page.goto("/login");
const btn = page.getByRole("button", { name: /sign in with github/i });
await expect(btn).toBeVisible();
});
// ── OAuth callback flow ──────────────────────────────────────────────────────
test("OAuth callback flow completes and redirects", async ({ page }) => {
const fakeState = "teststate123";
// Pre-populate sessionStorage with the CSRF state before navigation.
await page.addInitScript((state) => {
sessionStorage.setItem("github-tracker:oauth-state", state);
}, fakeState);
// Mock the token exchange endpoint and the /user validation
await page.route("**/api/oauth/token", (route) =>
route.fulfill({
status: 200,
json: {
access_token: "ghu_fake",
token_type: "bearer",
scope: "repo read:org notifications",
},
})
);
await page.route("https://api.github.com/user", (route) =>
route.fulfill({
status: 200,
json: {
login: "testuser",
name: "Test User",
avatar_url: "https://github.com/testuser.png",
},
})
);
// After validateToken succeeds the callback navigates to '/' which redirects
// to /dashboard (onboardingComplete) or /onboarding. We need config set.
await page.addInitScript(() => {
localStorage.setItem(
"github-tracker:config",
JSON.stringify({
selectedOrgs: ["testorg"],
selectedRepos: [{ owner: "testorg", name: "testrepo", fullName: "testorg/testrepo" }],
onboardingComplete: true,
})
);
});
// Also intercept downstream dashboard API calls
await page.route("https://api.github.com/graphql", (route) =>
route.fulfill({
status: 200,
json: {
data: {
search: { issueCount: 0, pageInfo: { hasNextPage: false, endCursor: null }, nodes: [] },
rateLimit: { remaining: 5000, resetAt: new Date(Date.now() + 3600000).toISOString() },
},
},
})
);
await page.route("https://api.github.com/notifications*", (route) =>
route.fulfill({ status: 200, json: [] })
);
await page.goto(`/oauth/callback?code=fakecode&state=${fakeState}`);
// After successful auth the callback navigates to '/' which redirects
// to /dashboard (if onboardingComplete) or /onboarding (first login)
await expect(page).toHaveURL(/\/(dashboard|onboarding)/);
});
// ── Dashboard ────────────────────────────────────────────────────────────────
test("dashboard loads with tab bar visible", async ({ page }) => {
await setupAuth(page);
await page.goto("/dashboard");
const tablist = page.getByRole("tablist");
await expect(tablist).toBeVisible();
await expect(page.getByRole("tab", { name: /^issues/i })).toBeVisible();
await expect(
page.getByRole("tab", { name: /^pull requests/i })
).toBeVisible();
await expect(page.getByRole("tab", { name: /^actions/i })).toBeVisible();
});
test("switching tabs changes active tab indicator", async ({ page }) => {
await setupAuth(page);
await page.goto("/dashboard");
const issuesTab = page.getByRole("tab", { name: /^issues/i });
const prTab = page.getByRole("tab", { name: /^pull requests/i });
const actionsTab = page.getByRole("tab", { name: /^actions/i });
// Default tab should be issues (or whatever config says; we didn't set defaultTab)
await expect(issuesTab).toBeVisible();
// Click Pull Requests tab
await prTab.click();
await expect(prTab).toHaveAttribute("aria-selected", "true");
await expect(issuesTab).not.toHaveAttribute("aria-selected", "true");
// Click Actions tab
await actionsTab.click();
await expect(actionsTab).toHaveAttribute("aria-selected", "true");
});
test("dashboard shows empty state with no data", async ({ page }) => {
await setupAuth(page);
await page.goto("/dashboard");
// With empty mocked responses the dashboard should not show a loading spinner
// indefinitely — wait for the tab bar to appear then confirm no data rows
const tablist = page.getByRole("tablist");
await expect(tablist).toBeVisible();
// The issues tab content area should render (even if empty)
await expect(page.getByRole("main")).toBeVisible();
});