CI Testing with Playwright

BugDrop can be checked in CI without submitting a real report. The stable browser contract is the bugdrop:ready window event, a #bugdrop-host element, and controls inside its open Shadow DOM. The current trigger, dialog, and close selectors are .bd-trigger, .bd-modal, and .bd-close.

Source reviewed: official BugDrop README and repository on 2026-08-14. The example below was exercised against the hosted widget and the checksum-pinned copy used by this site.

Copy this smoke test

Register the event observer before navigation so a fast synchronous load cannot race the test:

import { test, expect } from "@playwright/test";

test("BugDrop loads and opens", async ({ page }) => {
  await page.addInitScript(() => {
    window.addEventListener(
      "bugdrop:ready",
      () => document.documentElement.setAttribute("data-bugdrop-ready", "true"),
      { once: true },
    );
  });

  await page.goto(process.env.PREVIEW_URL ?? "http://127.0.0.1:3000");

  await expect(page.locator("html")).toHaveAttribute("data-bugdrop-ready", "true");
  await expect(page.locator("#bugdrop-host")).toBeAttached();

  const trigger = page.locator("#bugdrop-host .bd-trigger");
  await expect(trigger).toBeVisible();
  await trigger.click();
  await expect(page.locator("#bugdrop-host .bd-modal")).toBeVisible();
  await expect(page.locator("#bugdrop-host .bd-close")).toBeVisible();
});

Playwright locators pierce an open Shadow DOM, so the descendant selectors above reach the widget without a custom helper. If your environment blocks third-party scripts, assert the request policy explicitly instead of adding a timeout.

Verify the loading element

The widget must execute from a normal script tag with its repository configuration attached. This assertion catches an accidental async, defer, duplicate, or wrong repository value:

test("uses one synchronous configured script", async ({ page }) => {
  await page.goto(process.env.PREVIEW_URL!);
  const scripts = page.locator(
    'script[src="https://bugdrop.neonwatty.workers.dev/widget.js"]',
  );
  await expect(scripts).toHaveCount(1);
  await expect(scripts).toHaveAttribute("data-repo", "owner/repo");
  await expect(scripts).not.toHaveAttribute("async", /.*/);
  await expect(scripts).not.toHaveAttribute("defer", /.*/);
});

Check preview-only loading

For the Vercel preview workflow, run the smoke test against a Preview Deployment. Run this negative check against the production URL:

test("production omits preview feedback", async ({ page }) => {
  await page.goto(process.env.PRODUCTION_URL!);
  await expect(
    page.locator('script[data-label="Preview feedback"]'),
  ).toHaveCount(0);
  await expect(page.locator("#bugdrop-host")).toHaveCount(0);
});

The negative assertion applies to the preview-only integration. If production intentionally has a different BugDrop installation, distinguish it with a different label and assert the preview script itself is absent.

Run it in CI

npm install --save-dev @playwright/test
npx playwright install --with-deps chromium
npx playwright test e2e/bugdrop.spec.ts

Pin the widget for deterministic CI if your release process requires it, and review installation, configuration, and security before enabling screenshots on sensitive routes. A smoke test proves loading and interaction; only a deliberate end-to-end canary should create and then verify a real GitHub Issue.