Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

fixtures.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

fixtures.tsBlame247 lines · 1 contributor
a014defClaude1/**
2 * Shared test helpers for Gluecron E2E tests.
3 *
4 * All helpers accept a Playwright `Page` instance (or raw fetch for API calls)
5 * so they integrate naturally with Playwright's context/browser model.
6 */
7
8import { type Page } from "@playwright/test";
9import * as path from "path";
10import * as os from "os";
11import * as fs from "fs/promises";
12
13// ---------------------------------------------------------------------------
14// Constants
15// ---------------------------------------------------------------------------
16
17export const BASE_URL = process.env.E2E_BASE_URL ?? "http://localhost:3000";
18
19/** Default password used for all test accounts. */
20export const TEST_PASSWORD = "TestPass123!";
21
22// ---------------------------------------------------------------------------
23// Unique ID generator — keeps test data isolated even with parallel shards.
24// ---------------------------------------------------------------------------
25
26let _seq = 0;
27export function uid(prefix = "u"): string {
28 _seq++;
29 return `${prefix}${Date.now()}${_seq}`;
30}
31
32// ---------------------------------------------------------------------------
33// Auth helpers
34// ---------------------------------------------------------------------------
35
36/**
37 * Registers a fresh user via the web UI and leaves the page logged in.
38 * Returns the username so callers can build repo URLs.
39 */
40export async function createTestUser(
41 page: Page,
42 prefix = "tuser"
43): Promise<string> {
44 const username = uid(prefix);
45 const email = `${username}@test.example`;
46
47 await page.goto("/register");
48 await page.fill('input[name="username"]', username);
49 await page.fill('input[name="email"]', email);
50 await page.fill('input[name="password"]', TEST_PASSWORD);
51 await page.click('button[type="submit"]');
52
53 // After successful registration, server redirects to /dashboard or similar.
54 await page.waitForURL(/\/(dashboard|[a-z])/);
55
56 return username;
57}
58
59/**
60 * Logs an existing user in via the web UI.
61 */
62export async function loginUser(
63 page: Page,
64 username: string,
65 password = TEST_PASSWORD
66): Promise<void> {
67 await page.goto("/login");
68 await page.fill('input[name="username"]', username);
69 await page.fill('input[name="password"]', password);
70 await page.click('button[type="submit"]');
71 await page.waitForURL(/\/(dashboard|[a-z])/);
72}
73
74/**
75 * Logs the current user out via the UI.
76 */
77export async function logoutUser(page: Page): Promise<void> {
78 // Try the nav logout link first; fall back to direct form POST.
79 const logoutLink = page.locator('a[href="/logout"]').first();
80 if (await logoutLink.isVisible()) {
81 await logoutLink.click();
82 } else {
83 await page.goto("/logout");
84 }
85 await page.waitForURL(/\/(login|register|$)/);
86}
87
88// ---------------------------------------------------------------------------
89// Repository helpers
90// ---------------------------------------------------------------------------
91
92/**
93 * Creates a repository via the web UI.
94 * Assumes the user is already logged in.
95 * Returns the repo name.
96 */
97export async function createTestRepo(
98 page: Page,
99 owner: string,
100 namePrefix = "repo"
101): Promise<string> {
102 const repoName = uid(namePrefix);
103
104 await page.goto("/new");
105 await page.fill('input[name="name"]', repoName);
106
107 // Optional description
108 const descField = page.locator('input[name="description"], textarea[name="description"]');
109 if (await descField.isVisible()) {
110 await descField.fill("E2E test repo");
111 }
112
113 await page.click('button[type="submit"]');
114
115 // Should redirect to the new repo page.
116 await page.waitForURL(new RegExp(`/${owner}/${repoName}`));
117
118 return repoName;
119}
120
121// ---------------------------------------------------------------------------
122// Git helpers
123// ---------------------------------------------------------------------------
124
125/**
126 * Clones a repo to a temporary directory, adds a file, and pushes back.
127 * Uses HTTP credentials (username + password) embedded in the URL.
128 *
129 * Returns the temp directory path (caller can clean up).
130 */
131export async function pushTestCommit(opts: {
132 owner: string;
133 repo: string;
134 username: string;
135 password?: string;
136 fileName?: string;
137 fileContent?: string;
138 commitMsg?: string;
139 branch?: string;
140}): Promise<string> {
141 const {
142 owner,
143 repo,
144 username,
145 password = TEST_PASSWORD,
146 fileName = "README.md",
147 fileContent = `# ${repo}\n\nE2E test commit.\n`,
148 commitMsg = "Add test file",
149 branch = "main",
150 } = opts;
151
152 const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "gc-e2e-"));
153
154 const baseUrl = BASE_URL.replace("://", `://${username}:${encodeURIComponent(password)}@`);
155 const repoUrl = `${baseUrl}/${owner}/${repo}.git`;
156
157 // git init + set up remote
158 await spawnGit(tmpDir, ["init", "-b", branch]);
159 await spawnGit(tmpDir, ["remote", "add", "origin", repoUrl]);
160 await spawnGit(tmpDir, ["config", "user.email", `${username}@test.example`]);
161 await spawnGit(tmpDir, ["config", "user.name", username]);
162
163 // Write file
164 await fs.writeFile(path.join(tmpDir, fileName), fileContent, "utf8");
165
166 // Commit and push
167 await spawnGit(tmpDir, ["add", "."]);
168 await spawnGit(tmpDir, ["commit", "-m", commitMsg]);
169 await spawnGit(tmpDir, ["push", "-u", "origin", branch]);
170
171 return tmpDir;
172}
173
174/**
175 * Pushes a second commit on a new branch (useful for creating PRs).
176 * Returns the branch name.
177 */
178export async function pushFeatureBranch(opts: {
179 repoDir: string;
180 username: string;
181 branchName?: string;
182 fileName?: string;
183 fileContent?: string;
184 commitMsg?: string;
185}): Promise<string> {
186 const {
187 repoDir,
188 username,
189 branchName = uid("feat-"),
190 fileName = "feature.md",
191 fileContent = "Feature branch file.\n",
192 commitMsg = "Add feature file",
193 } = opts;
194
195 await spawnGit(repoDir, ["config", "user.email", `${username}@test.example`]);
196 await spawnGit(repoDir, ["config", "user.name", username]);
197 await spawnGit(repoDir, ["checkout", "-b", branchName]);
198
199 await fs.writeFile(path.join(repoDir, fileName), fileContent, "utf8");
200 await spawnGit(repoDir, ["add", "."]);
201 await spawnGit(repoDir, ["commit", "-m", commitMsg]);
202 await spawnGit(repoDir, ["push", "-u", "origin", branchName]);
203
204 return branchName;
205}
206
207// ---------------------------------------------------------------------------
208// Internal: spawn a git subprocess
209// ---------------------------------------------------------------------------
210
211async function spawnGit(cwd: string, args: string[]): Promise<string> {
212 const proc = Bun.spawn(["git", ...args], {
213 cwd,
214 env: {
215 ...process.env,
216 // Suppress interactive prompts
217 GIT_TERMINAL_PROMPT: "0",
218 GIT_ASKPASS: "echo",
219 },
220 stdout: "pipe",
221 stderr: "pipe",
222 });
223
224 const exitCode = await proc.exited;
225 const stdout = await new Response(proc.stdout).text();
226 const stderr = await new Response(proc.stderr).text();
227
228 if (exitCode !== 0) {
229 throw new Error(
230 `git ${args.join(" ")} failed (exit ${exitCode}):\n${stderr}\n${stdout}`
231 );
232 }
233
234 return stdout.trim();
235}
236
237// ---------------------------------------------------------------------------
238// Cleanup helper
239// ---------------------------------------------------------------------------
240
241export async function cleanupDir(dir: string): Promise<void> {
242 try {
243 await fs.rm(dir, { recursive: true, force: true });
244 } catch {
245 // Best effort
246 }
247}