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
claude/adoring-hopper-5x74bqclaude/affectionate-feynman-ykrf1hclaude/architecture-audit-design-wxprenclaude/build-status-update-3MXsfclaude/charming-meitner-mllb5rclaude/compare-gate-gluecron-s4mFQclaude/confident-faraday-tikcwbclaude/continue-work-XMTlIclaude/crontech-gluecron-deploy-7MIECclaude/crontech-platform-setup-SeKfwclaude/design-2026claude/ecstatic-ptolemy-jMdigclaude/enhance-github-integration-QNHdGclaude/fix-aa-loop-issue-PonMQclaude/fix-actions-and-processclaude/fix-desktop-errors-XqoW8claude/fix-red-workflowsclaude/fix-website-access-6FKJNclaude/gatetest-integration-hardeningclaude/github-audit-improvements-bDFr9claude/gluecron-launch-status-FoMRlclaude/hopeful-lamport-olfCTclaude/issue-to-pr-and-protectionsclaude/jolly-heisenberg-2sg1Qclaude/launch-preparation-QmTb6claude/new-session-xk1l7claude/plan-platform-architecture-kkN4yclaude/platform-analysis-roadmap-1nUGLclaude/platform-launch-assessment-8dWV8claude/polish-platform-release-AeDrUclaude/resume-previous-work-KzyLwclaude/review-crontech-handoff-qYEVqclaude/review-project-completeness-lHhS2claude/review-readme-docs-ulqPKclaude/serene-edison-rj87weclaude/setup-multi-repo-dev-BCwNQclaude/ship-fixes-and-tests-Jvz1cclaude/site-audit-competitive-pctlwgclaude/site-migration-vercel-XstpKclaude/standalone-product-repos-XHFTDcopilot/feat-smart-empty-states-keyboard-first-enhancementcopilot/feat-smart-morning-digest-review-context-restorecopilot/fix-and-process-workflowscopilot/update-ai-powered-code-reviewfeat/debt-mapfeat/push-policy-codeowners-hardeningfeat/smart-digest-contextfeat/stage-impactfeat/t1-secret-migrationfeat/u-polishfeat/w-self-hostfeat/w2-claude-configfix/agent-journey-orphan-sweepgatetest/auto-fix-1776586424172gatetest/auto-fix-1776586534814gatetest/auto-fix-1776590685143gatetest/auto-fix-1776590808199mainops/redeploy-retriggerstyle/dxt-cta-themeworktree-agent-a3377aad30d55da26worktree-agent-a7ef607b7ee1d6c74
issues.spec.ts7.7 KB · 235 lines
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/**
 * E2E — Issue tracker flows
 *
 * Covers: create issue, add comment, close issue, label management.
 */

import { test, expect } from "@playwright/test";
import {
  uid,
  TEST_PASSWORD,
  pushTestCommit,
  cleanupDir,
} from "./fixtures";

// ---------------------------------------------------------------------------
// Shared state — one user + one repo, one issue URL reused across suites.
// ---------------------------------------------------------------------------

let owner: string;
let repoName: string;
let tmpDir: string;
/** URL of an issue we create in beforeAll for the comment/close/label tests. */
let sharedIssueUrl: string;

test.beforeAll(async ({ browser }) => {
  owner = uid("issueuser");
  repoName = uid("issuerepo");

  const page = await browser.newPage();

  // Register
  await page.goto("/register");
  await page.fill('input[name="username"]', owner);
  await page.fill('input[name="email"]', `${owner}@test.example`);
  await page.fill('input[name="password"]', TEST_PASSWORD);
  await page.click('button[type="submit"]');
  await page.waitForURL(/\/(dashboard|[a-z])/);

  // Create repo
  await page.goto("/new");
  await page.fill('input[name="name"]', repoName);
  await page.click('button[type="submit"]');
  await page.waitForURL(new RegExp(`/${owner}/${repoName}`));

  await page.close();

  // Push a commit so the repo isn't empty (some routes need an existing HEAD)
  tmpDir = await pushTestCommit({
    owner,
    repo: repoName,
    username: owner,
    password: TEST_PASSWORD,
    fileName: "README.md",
    fileContent: `# ${repoName}\n`,
    commitMsg: "Initial commit",
  });

  // Create a shared issue via the web UI
  const page2 = await browser.newPage();
  await page2.goto("/login");
  await page2.fill('input[name="username"]', owner);
  await page2.fill('input[name="password"]', TEST_PASSWORD);
  await page2.click('button[type="submit"]');
  await page2.waitForURL(/\/(dashboard|[a-z])/);

  await page2.goto(`/${owner}/${repoName}/issues/new`);
  const titleInput = page2.locator('input[name="title"]');
  await titleInput.waitFor({ timeout: 8_000 });
  await titleInput.fill(`Shared issue ${uid("iss")}`);

  const bodyArea = page2.locator('textarea[name="body"]');
  if (await bodyArea.isVisible()) {
    await bodyArea.fill("Issue body for shared E2E issue.");
  }

  await page2.click('button[type="submit"]');
  await page2.waitForURL(new RegExp(`/${owner}/${repoName}/issues/\\d+`));
  sharedIssueUrl = page2.url();
  await page2.close();
});

test.afterAll(async () => {
  await cleanupDir(tmpDir);
});

// ---------------------------------------------------------------------------
// Helper
// ---------------------------------------------------------------------------

async function loginAsOwner(page: any): Promise<void> {
  await page.goto("/login");
  await page.fill('input[name="username"]', owner);
  await page.fill('input[name="password"]', TEST_PASSWORD);
  await page.click('button[type="submit"]');
  await page.waitForURL(/\/(dashboard|[a-z])/);
}

// ---------------------------------------------------------------------------
// Create issue
// ---------------------------------------------------------------------------

test.describe("Issue creation", () => {
  test("issues list page is accessible", async ({ page }) => {
    await page.goto(`/${owner}/${repoName}/issues`);
    await expect(page).toHaveURL(new RegExp(`/${owner}/${repoName}/issues`));
    await expect(page.locator("body")).not.toContainText(/500|Internal Server Error/i);
  });

  test("can open new issue form", async ({ page }) => {
    await loginAsOwner(page);
    await page.goto(`/${owner}/${repoName}/issues/new`);
    await expect(page.locator('input[name="title"]')).toBeVisible({ timeout: 8_000 });
  });

  test("can create a new issue", async ({ page }) => {
    await loginAsOwner(page);
    await page.goto(`/${owner}/${repoName}/issues/new`);

    const titleInput = page.locator('input[name="title"]');
    await titleInput.waitFor({ timeout: 8_000 });

    const issueTitle = `New issue ${uid("ni")}`;
    await titleInput.fill(issueTitle);

    const bodyArea = page.locator('textarea[name="body"]');
    if (await bodyArea.isVisible()) {
      await bodyArea.fill("This is an E2E-created issue.");
    }

    await page.click('button[type="submit"]');

    // Should redirect to the issue detail page
    await expect(page).toHaveURL(
      new RegExp(`/${owner}/${repoName}/issues/\\d+`)
    );
    await expect(page.locator("body")).toContainText(issueTitle);
  });

  test("issue appears in the issues list", async ({ page }) => {
    await page.goto(`/${owner}/${repoName}/issues`);
    // The shared issue created in beforeAll should be listed
    await expect(page.locator("body")).toContainText(/Shared issue|issue/i, {
      timeout: 8_000,
    });
  });
});

// ---------------------------------------------------------------------------
// Comment on issue
// ---------------------------------------------------------------------------

test.describe("Issue comments", () => {
  test("can post a comment on an issue", async ({ page }) => {
    await loginAsOwner(page);
    await page.goto(sharedIssueUrl);

    const commentBox = page.locator(
      'textarea[name="body"], textarea[name="comment"]'
    ).first();
    await expect(commentBox).toBeVisible({ timeout: 8_000 });

    const commentText = `E2E comment ${uid("cm")}`;
    await commentBox.fill(commentText);
    await page.click('button[type="submit"]');

    await expect(page.locator("body")).toContainText(commentText, {
      timeout: 8_000,
    });
  });
});

// ---------------------------------------------------------------------------
// Close issue
// ---------------------------------------------------------------------------

test.describe("Issue lifecycle", () => {
  let closeIssueUrl: string;

  test.beforeAll(async ({ browser }) => {
    // Create a fresh issue just for the close test
    const page = await browser.newPage();
    await loginAsOwner(page);

    await page.goto(`/${owner}/${repoName}/issues/new`);
    const titleInput = page.locator('input[name="title"]');
    await titleInput.waitFor({ timeout: 8_000 });
    await titleInput.fill(`Close-me issue ${uid("cl")}`);
    await page.click('button[type="submit"]');
    await page.waitForURL(new RegExp(`/${owner}/${repoName}/issues/\\d+`));
    closeIssueUrl = page.url();
    await page.close();
  });

  test("can close an open issue", async ({ page }) => {
    await loginAsOwner(page);
    await page.goto(closeIssueUrl);

    const closeBtn = page.locator(
      'button:has-text("Close"), button[value="close"], form[action*="close"] button'
    ).first();
    await expect(closeBtn).toBeVisible({ timeout: 8_000 });
    await closeBtn.click();

    await expect(page.locator("body")).toContainText(/closed/i, {
      timeout: 8_000,
    });
  });

  test("can reopen a closed issue", async ({ page }) => {
    await loginAsOwner(page);
    await page.goto(closeIssueUrl);

    const reopenBtn = page.locator(
      'button:has-text("Reopen"), button[value="reopen"], form[action*="reopen"] button'
    ).first();
    await expect(reopenBtn).toBeVisible({ timeout: 8_000 });
    await reopenBtn.click();

    await expect(page.locator("body")).toContainText(/open/i, {
      timeout: 8_000,
    });
  });
});

// ---------------------------------------------------------------------------
// Labels
// ---------------------------------------------------------------------------

test.describe("Issue labels", () => {
  test("labels page is accessible", async ({ page }) => {
    await page.goto(`/${owner}/${repoName}/labels`);
    // Page should load (may be empty if no labels yet)
    await expect(page.locator("body")).not.toContainText(/500|Internal Server Error/i);
  });
});