Support top-layer <dialog> recording & replay (#1503)

* chore: its important to run `yarn build:all` before running `yarn dev`

* feat: trigger showModal from rrdom and rrweb

* feat: Add support for replaying modal and non modal dialog elements

* chore: Update dev script to remove CLEAR_DIST_DIR flag

* Get modal recording and replay working

* DRY up dialog test and dedupe snapshot images

* feat: Refactor dialog test to use updated attribute name

* feat: Update dialog test to include rr_open attribute

* chore: Add npm dependency happy-dom@14.12.0

* Add more test cases for dialog

* Clean up naming

* Refactor dialog open code

* Revert changed code that doesn't do anything

* Add documentation for unimplemented type

* chore: Remove unnecessary comments in dialog.test.ts

* rename rr_open to rr_openMode

* Replace todo with a skipped test

* Add better logging for CI

* Rename rr_openMode to rr_open_mode

rrdom downcases all attribute names which made `rr_openMode` tricky to deal with

* Remove unused images

* Move after iframe append based on @YunFeng0817's comment
https://github.com/rrweb-io/rrweb/pull/1503#discussion_r1666363931

* Remove redundant dialog handling from rrdom.

rrdom already handles dialog element creation it's self

* Rename variables for dialog handling in rrweb replay module

* Update packages/rrdom/src/document.ts

---------

Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
This commit is contained in:
Justin Halsall
2026-04-01 12:00:00 +08:00
committed by GitHub
parent c110e1c21d
commit 5217a09c60
38 changed files with 1902 additions and 75 deletions

View File

@@ -1,10 +1,20 @@
import * as fs from 'fs';
import * as path from 'path';
import * as http from 'http';
import * as url from 'url';
import * as path from 'path';
import * as puppeteer from 'puppeteer';
import { vi, assert, describe, it, beforeAll, afterAll, expect } from 'vitest';
import { waitForRAF, getServerURL } from './utils';
import * as url from 'url';
import {
afterAll,
assert,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
} from 'vitest';
import { getServerURL, waitForRAF } from './utils';
const htmlFolder = path.join(__dirname, 'html');
const htmls = fs.readdirSync(htmlFolder).map((filePath) => {
@@ -60,6 +70,15 @@ function sanitizeSnapshot(snapshot: string): string {
return snapshot.replace(/localhost:[0-9]+/g, 'localhost:3030');
}
async function snapshot(page: puppeteer.Page, code: string): Promise<string> {
await waitForRAF(page);
const result = (await page.evaluate(`${code}
const snapshot = rrwebSnapshot.snapshot(document);
JSON.stringify(snapshot, null, 2);
`)) as string;
return result;
}
function assertSnapshot(snapshot: string): void {
expect(sanitizeSnapshot(snapshot)).toMatchSnapshot();
}
@@ -68,6 +87,7 @@ interface ISuite {
server: http.Server;
serverURL: string;
browser: puppeteer.Browser;
page: puppeteer.Page;
code: string;
}
@@ -431,6 +451,53 @@ describe('iframe integration tests', function (this: ISuite) {
});
});
describe('dialog integration tests', function (this: ISuite) {
vi.setConfig({ testTimeout: 30_000 });
let server: ISuite['server'];
let serverURL: ISuite['serverURL'];
let browser: ISuite['browser'];
let code: ISuite['code'];
let page: ISuite['page'];
beforeAll(async () => {
server = await startServer();
serverURL = getServerURL(server);
browser = await puppeteer.launch({
// headless: false,
});
code = fs.readFileSync(
path.resolve(__dirname, '../dist/rrweb-snapshot.umd.cjs'),
'utf-8',
);
});
beforeEach(async () => {
page = await browser.newPage();
page.on('console', (msg) => console.log(msg.text()));
await page.goto(`${serverURL}/html/dialog.html`, {
waitUntil: 'load',
});
});
afterAll(async () => {
await browser.close();
await server.close();
});
it('should capture open attribute for non modal dialogs', async () => {
page.evaluate('document.querySelector("dialog").show()');
const snapshotResult = await snapshot(page, code);
assertSnapshot(snapshotResult);
});
it('should capture open attribute for modal dialogs', async () => {
await page.evaluate('document.querySelector("dialog").showModal()');
const snapshotResult = await snapshot(page, code);
assertSnapshot(snapshotResult);
});
});
describe('shadow DOM integration tests', function (this: ISuite) {
vi.setConfig({ testTimeout: 30_000 });
let server: ISuite['server'];