Skip to content

Commit 63cc05e

Browse files
authored
Merge pull request #131 from mean-weasel/feat/screenshot-modes
Add configurable screenshot modes
2 parents 55a498b + c525570 commit 63cc05e

6 files changed

Lines changed: 216 additions & 25 deletions

File tree

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ node_modules/
99
playwright-report/
1010
test-results/
1111
coverage/
12+
.playwright-cli/
1213

1314
# Lock files
1415
package-lock.json

docs/website/configuration.mdx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,37 @@ Every feedback submission automatically captures the following system informatio
136136

137137
This information is included in every GitHub Issue body under a "System Info" section, giving your team the context they need to reproduce and fix bugs.
138138

139+
## Screenshot Behavior
140+
141+
Control how screenshots are collected during the feedback flow.
142+
143+
| Attribute | Default | Description |
144+
|-----------|---------|-------------|
145+
| `data-screenshot` | `"optional"` | Screenshot mode: `"optional"`, `"auto"`, or `"required"` |
146+
| `data-screenshot-scale` | `"2"` | Minimum pixel ratio for screenshot captures |
147+
148+
### Screenshot Modes
149+
150+
- **`optional`** -- Shows the screenshot checkbox checked by default. Users can choose full page, element, area, annotate, or skip.
151+
- **`auto`** -- Automatically captures a full-page screenshot after the form is submitted, without showing the manual screenshot picker.
152+
- **`required`** -- Requires a screenshot before submission. Users can choose full page, element, or area, but cannot skip the screenshot step.
153+
154+
```html
155+
<!-- Automatically attach a full-page screenshot -->
156+
<script
157+
src="https://bugdrop.neonwatty.workers.dev/widget.js"
158+
data-repo="owner/repo"
159+
data-screenshot="auto"
160+
></script>
161+
162+
<!-- Require a screenshot before submitting feedback -->
163+
<script
164+
src="https://bugdrop.neonwatty.workers.dev/widget.js"
165+
data-repo="owner/repo"
166+
data-screenshot="required"
167+
></script>
168+
```
169+
139170
## Custom Icon and Label
140171

141172
### Custom Icon

docs/website/getting-started.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ That is it. No servers to run, no databases to manage, no user accounts required
3535
## Key Features
3636

3737
- **One-line installation** -- Add a single script tag and BugDrop is live on your site
38-
- **Automatic screenshots** -- Users can attach screenshots captured client-side with html2canvas
38+
- **Configurable screenshots** -- Screenshots can be optional, automatic, or required
3939
- **System info capture** -- Browser, OS, viewport, language, and URL are automatically included
4040
- **GitHub-native** -- Issues land directly in your repository with proper labels and formatting
4141
- **Shadow DOM isolation** -- The widget never conflicts with your site's CSS

e2e/widget.spec.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2376,3 +2376,102 @@ test.describe('Screenshot Crash Prevention (#67)', () => {
23762376
await expect(annotationCanvas).toBeVisible({ timeout: 30000 });
23772377
});
23782378
});
2379+
2380+
test.describe('Screenshot Mode Configuration', () => {
2381+
const STUB_PNG =
2382+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
2383+
2384+
async function setupInstalledApp(page: Page) {
2385+
await page.route('**/api/check/**', async route => {
2386+
await route.fulfill({
2387+
status: 200,
2388+
contentType: 'application/json',
2389+
body: JSON.stringify({ installed: true }),
2390+
});
2391+
});
2392+
}
2393+
2394+
async function setupSuccessfulSubmit(page: Page) {
2395+
let payload: Record<string, unknown> | null = null;
2396+
await page.route('**/feedback', async route => {
2397+
payload = route.request().postDataJSON();
2398+
await route.fulfill({
2399+
status: 200,
2400+
contentType: 'application/json',
2401+
body: JSON.stringify({ success: true, issueNumber: 1, issueUrl: '#', isPublic: false }),
2402+
});
2403+
});
2404+
return () => payload;
2405+
}
2406+
2407+
async function mockSuccessfulCapture(page: Page) {
2408+
await page.addInitScript(`window.__bugdropMockToPng = function() {
2409+
window.__autoCaptureCount = (window.__autoCaptureCount || 0) + 1;
2410+
return Promise.resolve('${STUB_PNG}');
2411+
};`);
2412+
}
2413+
2414+
async function openForm(page: Page) {
2415+
const host = page.locator('#bugdrop-host');
2416+
await expect(host.locator('css=.bd-trigger')).toBeVisible({ timeout: 5000 });
2417+
await host.locator('css=.bd-trigger').click();
2418+
2419+
const getStartedBtn = host.locator('css=[data-action="continue"]');
2420+
await expect(getStartedBtn).toBeVisible({ timeout: 5000 });
2421+
await getStartedBtn.click();
2422+
2423+
const titleInput = host.locator('css=#title');
2424+
await expect(titleInput).toBeVisible({ timeout: 5000 });
2425+
await titleInput.fill('Screenshot mode test');
2426+
return host;
2427+
}
2428+
2429+
test('auto mode captures and submits a screenshot without the manual screenshot step', async ({
2430+
page,
2431+
}) => {
2432+
await setupInstalledApp(page);
2433+
await mockSuccessfulCapture(page);
2434+
const getPayload = await setupSuccessfulSubmit(page);
2435+
2436+
await page.goto('/test/?screenshot=auto');
2437+
const host = await openForm(page);
2438+
2439+
await expect(host.locator('css=#include-screenshot')).not.toBeAttached();
2440+
await host.locator('css=#submit-btn').click();
2441+
2442+
await expect(host.locator('css=.bd-success-icon')).toBeVisible({ timeout: 10000 });
2443+
await expect(host.locator('css=[data-action="capture"]')).not.toBeAttached();
2444+
await expect(host.locator('css=#annotation-canvas')).not.toBeAttached();
2445+
2446+
const payload = getPayload();
2447+
expect(payload?.screenshot).toBe(STUB_PNG);
2448+
expect(
2449+
await page.evaluate(
2450+
() => (window as Window & { __autoCaptureCount?: number }).__autoCaptureCount
2451+
)
2452+
).toBe(1);
2453+
});
2454+
2455+
test('required mode removes the opt-out path and requires a capture before submit', async ({
2456+
page,
2457+
}) => {
2458+
await setupInstalledApp(page);
2459+
await mockSuccessfulCapture(page);
2460+
const getPayload = await setupSuccessfulSubmit(page);
2461+
2462+
await page.goto('/test/?screenshot=required');
2463+
const host = await openForm(page);
2464+
2465+
await expect(host.locator('css=#include-screenshot')).not.toBeAttached();
2466+
await host.locator('css=#submit-btn').click();
2467+
2468+
await expect(host.locator('css=[data-action="skip"]')).not.toBeAttached();
2469+
await host.locator('css=[data-action="capture"]').click();
2470+
2471+
await expect(host.locator('css=#annotation-canvas')).toBeVisible({ timeout: 10000 });
2472+
await host.locator('css=[data-action="done"]').click();
2473+
2474+
await expect(host.locator('css=.bd-success-icon')).toBeVisible({ timeout: 10000 });
2475+
expect(getPayload()?.screenshot).toEqual(expect.stringMatching(/^data:image\/png;base64,/));
2476+
});
2477+
});

public/test/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ <h2>How It Works</h2>
604604
var script = document.getElementById('bugdrop-script');
605605
if (!script) return;
606606
var params = new URLSearchParams(location.search);
607-
var mapping = { theme: 'data-theme', bg: 'data-bg' };
607+
var mapping = { theme: 'data-theme', bg: 'data-bg', screenshot: 'data-screenshot' };
608608
Object.keys(mapping).forEach(function (key) {
609609
var v = params.get(key);
610610
if (v != null) script.setAttribute(mapping[key], v);

0 commit comments

Comments
 (0)