@vitest/browser 5.0.0-beta.4 → 5.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,8 +26,8 @@
26
26
  {__VITEST_INJECTOR__}
27
27
  {__VITEST_ERROR_CATCHER__}
28
28
  {__VITEST_SCRIPTS__}
29
- <script type="module" crossorigin src="/__vitest_browser__/orchestrator-B44yH1M4.js"></script>
30
- <link rel="modulepreload" crossorigin href="/__vitest_browser__/defineProperty-C3k2g8Sk.js">
29
+ <script type="module" crossorigin src="/__vitest_browser__/orchestrator-pKob2Z-i.js"></script>
30
+ <link rel="modulepreload" crossorigin href="/__vitest_browser__/defineProperty-WHpdAQXR.js">
31
31
  </head>
32
32
  <body>
33
33
  <div id="vitest-tester"></div>
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" href="{__VITEST_FAVICON__}" type="image/svg+xml">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Vitest Browser Tester</title>
8
- <script type="module" crossorigin src="/__vitest_browser__/tester-kAU8uxhk.js"></script>
9
- <link rel="modulepreload" crossorigin href="/__vitest_browser__/defineProperty-C3k2g8Sk.js">
8
+ <script type="module" crossorigin src="/__vitest_browser__/tester-DdZeFrjo.js"></script>
9
+ <link rel="modulepreload" crossorigin href="/__vitest_browser__/defineProperty-WHpdAQXR.js">
10
10
  </head>
11
11
  <body>
12
12
  </body>
@@ -1,4 +1,4 @@
1
- import type { Task } from "@vitest/runner";
1
+ import type { RunnerTask as Task } from "vitest";
2
2
  import type { BrowserTraceEntryKind } from "vitest/browser";
3
3
  import type { SerializedLocator } from "./locators.js";
4
4
  export interface BrowserTraceData {
package/dist/context.js CHANGED
@@ -245,11 +245,10 @@ function processTimeoutOptions(options_) {
245
245
  const currentTime = now();
246
246
  const endTime = startTime + timeout;
247
247
  const remainingTime = Math.floor(endTime - currentTime);
248
- if (remainingTime <= 0) {
249
- return options_;
250
- }
251
- // give us some time to process the timeout
252
- options_.timeout = remainingTime - 100;
248
+ // keep some buffer to process the timeout, but always hand the provider a
249
+ // positive value so it surfaces a descriptive, source-mapped locator error
250
+ // instead of letting the task timer win the race with a generic timeout
251
+ options_.timeout = Math.max(remainingTime - 100, 1);
253
252
  return options_;
254
253
  }
255
254
  const provider$1 = getBrowserState().provider;
@@ -1,4 +1,4 @@
1
- import{Snapshots as e,recordArtifact as t,expect as n,chai as r}from"vitest";import{getType as i}from"vitest/internal/browser";import{b as a,e as o,i as s,g as c,n as l,a as u,k as d,c as f,d as ee,f as te,h as ne,j as re,l as ie,m as ae,o as oe,p as se,q as ce,r as le,s as ue,t as p,_ as m,u as h,L as g,v as de,w as fe,x as pe,y as me,z as he,A as ge,B as _e,C as ve,D as ye,E as be}from"./locators-CPBpJv8y.js";import{server as xe}from"vitest/browser";var Se=Object.defineProperty,Ce=(e,t)=>{let n={};for(var r in e)Se(n,r,{get:e[r],enumerable:!0});return Se(n,Symbol.toStringTag,{value:`Module`}),n};function we(e,t,n={}){let r=Te(e,t,n);if(r.errors.length)throw Error(r.errors[0].message);return r.fragment}function Te(e,t,n={}){let r=new e.LineCounter,i={keepSourceTokens:!0,lineCounter:r,...n},a=e.parseDocument(t,i),o=[],s=e=>[r.linePos(e[0]),r.linePos(e[1])],c=e=>{o.push({message:e.message,range:[r.linePos(e.pos[0]),r.linePos(e.pos[1])]})},l=(t,n)=>{for(let r of n.items){if(r instanceof e.Scalar&&typeof r.value==`string`){let e=v.parse(r,i,o);e&&(t.children=t.children||[],t.children.push(e));continue}if(r instanceof e.YAMLMap){u(t,r);continue}o.push({message:`Sequence items should be strings or maps`,range:s(r.range||n.range)})}},u=(t,n)=>{for(let r of n.items){if(t.children=t.children||[],!(r.key instanceof e.Scalar&&typeof r.key.value==`string`)){o.push({message:`Only string keys are supported`,range:s(r.key.range||n.range)});continue}let a=r.key,c=r.value;if(a.value===`text`){if(!(c instanceof e.Scalar&&typeof c.value==`string`)){o.push({message:`Text value should be a string`,range:s(r.value.range||n.range)});continue}t.children.push({kind:`text`,text:_(c.value)});continue}if(a.value===`/children`){if(!(c instanceof e.Scalar&&typeof c.value==`string`)||c.value!==`contain`&&c.value!==`equal`&&c.value!==`deep-equal`){o.push({message:`Strict value should be "contain", "equal" or "deep-equal"`,range:s(r.value.range||n.range)});continue}t.containerMode=c.value;continue}if(a.value.startsWith(`/`)){if(!(c instanceof e.Scalar&&typeof c.value==`string`)){o.push({message:`Property value should be a string`,range:s(r.value.range||n.range)});continue}t.props=t.props??{},t.props[a.value.slice(1)]=_(c.value);continue}let u=v.parse(a,i,o);if(u){if(c instanceof e.Scalar){let e=typeof c.value;if(e!==`string`&&e!==`number`&&e!==`boolean`){o.push({message:`Node value should be a string or a sequence`,range:s(r.value.range||n.range)});continue}t.children.push({...u,children:[{kind:`text`,text:_(String(c.value))}]});continue}if(c instanceof e.YAMLSeq){t.children.push(u),l(u,c);continue}o.push({message:`Map values should be strings or sequences`,range:s(r.value.range||n.range)})}}},d={kind:`role`,role:`fragment`};return a.errors.forEach(c),o.length?{errors:o,fragment:d}:a.contents?(a.contents instanceof e.YAMLSeq||o.push({message:`Aria snapshot must be a YAML sequence, elements starting with " -"`,range:s(a.contents.range)}),o.length?{errors:o,fragment:d}:(l(d,a.contents),o.length?{errors:o,fragment:Ee}:d.children?.length===1&&(!d.containerMode||d.containerMode===`contain`)?{fragment:d.children[0],errors:[]}:{fragment:d,errors:[]})):{fragment:d,errors:[]}}const Ee={kind:`role`,role:`fragment`};function De(e){return e.replace(/[\u200b\u00ad]/g,``).replace(/[\r\n\s\t]+/g,` `).trim()}function _(e){return{raw:e,normalized:De(e)}}var v=class e{static parse(t,n,r){try{return new e(t.value)._parse()}catch(e){if(e instanceof y){let i=n.prettyErrors===!1?e.message:`${e.message}:\n\n${t.value}\n${` `.repeat(e.pos)}^\n`;return r.push({message:i,range:[n.lineCounter.linePos(t.range[0]),n.lineCounter.linePos(t.range[0]+e.pos)]}),null}throw e}}constructor(e){m(this,`_input`,void 0),m(this,`_pos`,void 0),m(this,`_length`,void 0),this._input=e,this._pos=0,this._length=e.length}_peek(){return this._input[this._pos]||``}_next(){return this._pos<this._length?this._input[this._pos++]:null}_eof(){return this._pos>=this._length}_isWhitespace(){return!this._eof()&&/\s/.test(this._peek())}_skipWhitespace(){for(;this._isWhitespace();)this._pos++}_readIdentifier(e){this._eof()&&this._throwError(`Unexpected end of input when expecting ${e}`);let t=this._pos;for(;!this._eof()&&/[a-zA-Z]/.test(this._peek());)this._pos++;return this._input.slice(t,this._pos)}_readString(){let e=``,t=!1;for(;!this._eof();){let n=this._next();if(t)e+=n,t=!1;else if(n===`\\`)t=!0;else if(n===`"`)return e;else e+=n}this._throwError(`Unterminated string`)}_throwError(e,t=0){throw new y(e,t||this._pos)}_readRegex(){let e=``,t=!1,n=!1;for(;!this._eof();){let r=this._next();if(t)e+=r,t=!1;else if(r===`\\`)t=!0,e+=r;else if(r===`/`&&!n)return{pattern:e};else r===`[`?(n=!0,e+=r):r===`]`&&n?(e+=r,n=!1):e+=r}this._throwError(`Unterminated regex`)}_readStringOrRegex(){let e=this._peek();return e===`"`?(this._next(),De(this._readString())):e===`/`?(this._next(),this._readRegex()):null}_readAttributes(e){let t=this._pos;for(;this._skipWhitespace(),this._peek()===`[`;){this._next(),this._skipWhitespace(),t=this._pos;let n=this._readIdentifier(`attribute`);this._skipWhitespace();let r=``;if(this._peek()===`=`)for(this._next(),this._skipWhitespace(),t=this._pos;this._peek()!==`]`&&!this._isWhitespace()&&!this._eof();)r+=this._next();this._skipWhitespace(),this._peek()!==`]`&&this._throwError(`Expected ]`),this._next(),this._applyAttribute(e,n,r||`true`,t)}}_parse(){this._skipWhitespace();let e=this._readIdentifier(`role`);e===`fragment`&&this._throwError(`Invalid role "fragment"`),this._skipWhitespace();let t={kind:`role`,role:e,name:this._readStringOrRegex()||void 0};return this._readAttributes(t),this._skipWhitespace(),this._eof()||this._throwError(`Unexpected input`),t}_applyAttribute(e,t,n,r){if(t===`checked`){this._assert(n===`true`||n===`false`||n===`mixed`,`Value of "checked" attribute must be a boolean or "mixed"`,r),e.checked=n===`true`?!0:n===`false`?!1:`mixed`;return}if(t===`disabled`){this._assert(n===`true`||n===`false`,`Value of "disabled" attribute must be a boolean`,r),e.disabled=n===`true`;return}if(t===`expanded`){this._assert(n===`true`||n===`false`,`Value of "expanded" attribute must be a boolean`,r),e.expanded=n===`true`;return}if(t===`active`){this._assert(n===`true`||n===`false`,`Value of "active" attribute must be a boolean`,r),e.active=n===`true`;return}if(t===`level`){this._assert(!Number.isNaN(Number(n)),`Value of "level" attribute must be a number`,r),e.level=Number(n);return}if(t===`pressed`){this._assert(n===`true`||n===`false`||n===`mixed`,`Value of "pressed" attribute must be a boolean or "mixed"`,r),e.pressed=n===`true`?!0:n===`false`?!1:`mixed`;return}if(t===`selected`){this._assert(n===`true`||n===`false`,`Value of "selected" attribute must be a boolean`,r),e.selected=n===`true`;return}this._assert(!1,`Unsupported attribute [${t}]`,r)}_assert(e,t,n){e||this._throwError(t||`Assertion error`,n)}},y=class extends Error{constructor(e,t){super(e),m(this,`pos`,void 0),this.pos=t}};function b(e,t){return t?e?typeof t==`string`?e===t:!!e.match(new RegExp(t.pattern)):!1:!0}function x(e,t){if(!t?.normalized)return!0;if(!e)return!1;if(e===t.normalized||e===t.raw)return!0;let n=C(t);return n?!!e.match(n):!1}const S=Symbol(`cachedRegex`);function C(e){if(e[S]!==void 0)return e[S];let{raw:t}=e,n=t.startsWith(`/`)&&t.endsWith(`/`)&&t.length>1,r;try{r=n?new RegExp(t.slice(1,-1)):null}catch{r=null}return e[S]=r,r}function w(e,t,n){if(typeof e==`string`&&t.kind===`text`)return x(e,t.text);if(typeof e!=`object`||!e||t.kind!==`role`||t.role!==`fragment`&&t.role!==e.role||t.checked!==void 0&&t.checked!==e.checked||t.disabled!==void 0&&t.disabled!==e.disabled||t.expanded!==void 0&&t.expanded!==e.expanded||t.level!==void 0&&t.level!==e.level||t.pressed!==void 0&&t.pressed!==e.pressed||t.selected!==void 0&&t.selected!==e.selected||!b(e.name,t.name))return!1;if(t.props){for(let[n,r]of Object.entries(t.props))if(!x(e.props[n]||``,r))return!1}return t.containerMode===`contain`?E(e.children||[],t.children||[]):t.containerMode===`equal`?T(e.children||[],t.children||[],!1):t.containerMode===`deep-equal`||n?T(e.children||[],t.children||[],!0):E(e.children||[],t.children||[])}function T(e,t,n){if(t.length!==e.length)return!1;for(let r=0;r<t.length;++r)if(!w(e[r],t[r],n))return!1;return!0}function E(e,t){if(t.length>e.length)return!1;let n=e.slice(),r=t.slice();for(let e of r){let t=n.shift();for(;t&&!w(t,e,!1);)t=n.shift();if(!t)return!1}return!0}var Oe=Ce({LineCounter:()=>ke,Scalar:()=>D,YAMLError:()=>A,YAMLMap:()=>O,YAMLSeq:()=>k,parseDocument:()=>Ae}),D=class{constructor(e,t=[0,0,0]){m(this,`value`,void 0),m(this,`range`,void 0),this.value=e,this.range=t}},O=class{constructor(e=[0,0,0]){m(this,`items`,void 0),m(this,`range`,void 0),this.items=[],this.range=e}},k=class{constructor(e=[0,0,0]){m(this,`items`,void 0),m(this,`range`,void 0),this.items=[],this.range=e}},ke=class{constructor(){m(this,`lineStarts`,[0])}addNewLine(e){e>this.lineStarts[this.lineStarts.length-1]&&this.lineStarts.push(e)}linePos(e){let t=0,n=this.lineStarts.length-1;for(;t<n;){let r=t+n+1>>1;this.lineStarts[r]<=e?t=r:n=r-1}return{line:t+1,col:e-this.lineStarts[t]+1}}},A=class extends Error{constructor(e,t){super(e),m(this,`pos`,void 0),this.pos=t}};function Ae(e,t={}){let n=t.lineCounter,r=[];if(n)for(let t=0;t<e.length;t++)e[t]===`
1
+ import{Snapshots as e,recordArtifact as t,expect as n,chai as r}from"vitest";import{getType as i}from"vitest/internal/browser";import{b as a,e as o,i as s,g as c,n as l,a as u,k as d,c as f,d as ee,f as te,h as ne,j as re,l as ie,m as ae,o as oe,p as se,q as ce,r as le,s as ue,t as p,_ as m,u as h,L as g,v as de,w as fe,x as pe,y as me,z as he,A as ge,B as _e,C as ve,D as ye,E as be}from"./locators-CVQtUwrj.js";import{server as xe}from"vitest/browser";var Se=Object.defineProperty,Ce=(e,t)=>{let n={};for(var r in e)Se(n,r,{get:e[r],enumerable:!0});return Se(n,Symbol.toStringTag,{value:`Module`}),n};function we(e,t,n={}){let r=Te(e,t,n);if(r.errors.length)throw Error(r.errors[0].message);return r.fragment}function Te(e,t,n={}){let r=new e.LineCounter,i={keepSourceTokens:!0,lineCounter:r,...n},a=e.parseDocument(t,i),o=[],s=e=>[r.linePos(e[0]),r.linePos(e[1])],c=e=>{o.push({message:e.message,range:[r.linePos(e.pos[0]),r.linePos(e.pos[1])]})},l=(t,n)=>{for(let r of n.items){if(r instanceof e.Scalar&&typeof r.value==`string`){let e=v.parse(r,i,o);e&&(t.children=t.children||[],t.children.push(e));continue}if(r instanceof e.YAMLMap){u(t,r);continue}o.push({message:`Sequence items should be strings or maps`,range:s(r.range||n.range)})}},u=(t,n)=>{for(let r of n.items){if(t.children=t.children||[],!(r.key instanceof e.Scalar&&typeof r.key.value==`string`)){o.push({message:`Only string keys are supported`,range:s(r.key.range||n.range)});continue}let a=r.key,c=r.value;if(a.value===`text`){if(!(c instanceof e.Scalar&&typeof c.value==`string`)){o.push({message:`Text value should be a string`,range:s(r.value.range||n.range)});continue}t.children.push({kind:`text`,text:_(c.value)});continue}if(a.value===`/children`){if(!(c instanceof e.Scalar&&typeof c.value==`string`)||c.value!==`contain`&&c.value!==`equal`&&c.value!==`deep-equal`){o.push({message:`Strict value should be "contain", "equal" or "deep-equal"`,range:s(r.value.range||n.range)});continue}t.containerMode=c.value;continue}if(a.value.startsWith(`/`)){if(!(c instanceof e.Scalar&&typeof c.value==`string`)){o.push({message:`Property value should be a string`,range:s(r.value.range||n.range)});continue}t.props=t.props??{},t.props[a.value.slice(1)]=_(c.value);continue}let u=v.parse(a,i,o);if(u){if(c instanceof e.Scalar){let e=typeof c.value;if(e!==`string`&&e!==`number`&&e!==`boolean`){o.push({message:`Node value should be a string or a sequence`,range:s(r.value.range||n.range)});continue}t.children.push({...u,children:[{kind:`text`,text:_(String(c.value))}]});continue}if(c instanceof e.YAMLSeq){t.children.push(u),l(u,c);continue}o.push({message:`Map values should be strings or sequences`,range:s(r.value.range||n.range)})}}},d={kind:`role`,role:`fragment`};return a.errors.forEach(c),o.length?{errors:o,fragment:d}:a.contents?(a.contents instanceof e.YAMLSeq||o.push({message:`Aria snapshot must be a YAML sequence, elements starting with " -"`,range:s(a.contents.range)}),o.length?{errors:o,fragment:d}:(l(d,a.contents),o.length?{errors:o,fragment:Ee}:d.children?.length===1&&(!d.containerMode||d.containerMode===`contain`)?{fragment:d.children[0],errors:[]}:{fragment:d,errors:[]})):{fragment:d,errors:[]}}const Ee={kind:`role`,role:`fragment`};function De(e){return e.replace(/[\u200b\u00ad]/g,``).replace(/[\r\n\s\t]+/g,` `).trim()}function _(e){return{raw:e,normalized:De(e)}}var v=class e{static parse(t,n,r){try{return new e(t.value)._parse()}catch(e){if(e instanceof y){let i=n.prettyErrors===!1?e.message:`${e.message}:\n\n${t.value}\n${` `.repeat(e.pos)}^\n`;return r.push({message:i,range:[n.lineCounter.linePos(t.range[0]),n.lineCounter.linePos(t.range[0]+e.pos)]}),null}throw e}}constructor(e){m(this,`_input`,void 0),m(this,`_pos`,void 0),m(this,`_length`,void 0),this._input=e,this._pos=0,this._length=e.length}_peek(){return this._input[this._pos]||``}_next(){return this._pos<this._length?this._input[this._pos++]:null}_eof(){return this._pos>=this._length}_isWhitespace(){return!this._eof()&&/\s/.test(this._peek())}_skipWhitespace(){for(;this._isWhitespace();)this._pos++}_readIdentifier(e){this._eof()&&this._throwError(`Unexpected end of input when expecting ${e}`);let t=this._pos;for(;!this._eof()&&/[a-zA-Z]/.test(this._peek());)this._pos++;return this._input.slice(t,this._pos)}_readString(){let e=``,t=!1;for(;!this._eof();){let n=this._next();if(t)e+=n,t=!1;else if(n===`\\`)t=!0;else if(n===`"`)return e;else e+=n}this._throwError(`Unterminated string`)}_throwError(e,t=0){throw new y(e,t||this._pos)}_readRegex(){let e=``,t=!1,n=!1;for(;!this._eof();){let r=this._next();if(t)e+=r,t=!1;else if(r===`\\`)t=!0,e+=r;else if(r===`/`&&!n)return{pattern:e};else r===`[`?(n=!0,e+=r):r===`]`&&n?(e+=r,n=!1):e+=r}this._throwError(`Unterminated regex`)}_readStringOrRegex(){let e=this._peek();return e===`"`?(this._next(),De(this._readString())):e===`/`?(this._next(),this._readRegex()):null}_readAttributes(e){let t=this._pos;for(;this._skipWhitespace(),this._peek()===`[`;){this._next(),this._skipWhitespace(),t=this._pos;let n=this._readIdentifier(`attribute`);this._skipWhitespace();let r=``;if(this._peek()===`=`)for(this._next(),this._skipWhitespace(),t=this._pos;this._peek()!==`]`&&!this._isWhitespace()&&!this._eof();)r+=this._next();this._skipWhitespace(),this._peek()!==`]`&&this._throwError(`Expected ]`),this._next(),this._applyAttribute(e,n,r||`true`,t)}}_parse(){this._skipWhitespace();let e=this._readIdentifier(`role`);e===`fragment`&&this._throwError(`Invalid role "fragment"`),this._skipWhitespace();let t={kind:`role`,role:e,name:this._readStringOrRegex()||void 0};return this._readAttributes(t),this._skipWhitespace(),this._eof()||this._throwError(`Unexpected input`),t}_applyAttribute(e,t,n,r){if(t===`checked`){this._assert(n===`true`||n===`false`||n===`mixed`,`Value of "checked" attribute must be a boolean or "mixed"`,r),e.checked=n===`true`?!0:n===`false`?!1:`mixed`;return}if(t===`disabled`){this._assert(n===`true`||n===`false`,`Value of "disabled" attribute must be a boolean`,r),e.disabled=n===`true`;return}if(t===`expanded`){this._assert(n===`true`||n===`false`,`Value of "expanded" attribute must be a boolean`,r),e.expanded=n===`true`;return}if(t===`active`){this._assert(n===`true`||n===`false`,`Value of "active" attribute must be a boolean`,r),e.active=n===`true`;return}if(t===`level`){this._assert(!Number.isNaN(Number(n)),`Value of "level" attribute must be a number`,r),e.level=Number(n);return}if(t===`pressed`){this._assert(n===`true`||n===`false`||n===`mixed`,`Value of "pressed" attribute must be a boolean or "mixed"`,r),e.pressed=n===`true`?!0:n===`false`?!1:`mixed`;return}if(t===`selected`){this._assert(n===`true`||n===`false`,`Value of "selected" attribute must be a boolean`,r),e.selected=n===`true`;return}this._assert(!1,`Unsupported attribute [${t}]`,r)}_assert(e,t,n){e||this._throwError(t||`Assertion error`,n)}},y=class extends Error{constructor(e,t){super(e),m(this,`pos`,void 0),this.pos=t}};function b(e,t){return t?e?typeof t==`string`?e===t:!!e.match(new RegExp(t.pattern)):!1:!0}function x(e,t){if(!t?.normalized)return!0;if(!e)return!1;if(e===t.normalized||e===t.raw)return!0;let n=C(t);return n?!!e.match(n):!1}const S=Symbol(`cachedRegex`);function C(e){if(e[S]!==void 0)return e[S];let{raw:t}=e,n=t.startsWith(`/`)&&t.endsWith(`/`)&&t.length>1,r;try{r=n?new RegExp(t.slice(1,-1)):null}catch{r=null}return e[S]=r,r}function w(e,t,n){if(typeof e==`string`&&t.kind===`text`)return x(e,t.text);if(typeof e!=`object`||!e||t.kind!==`role`||t.role!==`fragment`&&t.role!==e.role||t.checked!==void 0&&t.checked!==e.checked||t.disabled!==void 0&&t.disabled!==e.disabled||t.expanded!==void 0&&t.expanded!==e.expanded||t.level!==void 0&&t.level!==e.level||t.pressed!==void 0&&t.pressed!==e.pressed||t.selected!==void 0&&t.selected!==e.selected||!b(e.name,t.name))return!1;if(t.props){for(let[n,r]of Object.entries(t.props))if(!x(e.props[n]||``,r))return!1}return t.containerMode===`contain`?E(e.children||[],t.children||[]):t.containerMode===`equal`?T(e.children||[],t.children||[],!1):t.containerMode===`deep-equal`||n?T(e.children||[],t.children||[],!0):E(e.children||[],t.children||[])}function T(e,t,n){if(t.length!==e.length)return!1;for(let r=0;r<t.length;++r)if(!w(e[r],t[r],n))return!1;return!0}function E(e,t){if(t.length>e.length)return!1;let n=e.slice(),r=t.slice();for(let e of r){let t=n.shift();for(;t&&!w(t,e,!1);)t=n.shift();if(!t)return!1}return!0}var Oe=Ce({LineCounter:()=>ke,Scalar:()=>D,YAMLError:()=>A,YAMLMap:()=>O,YAMLSeq:()=>k,parseDocument:()=>Ae}),D=class{constructor(e,t=[0,0,0]){m(this,`value`,void 0),m(this,`range`,void 0),this.value=e,this.range=t}},O=class{constructor(e=[0,0,0]){m(this,`items`,void 0),m(this,`range`,void 0),this.items=[],this.range=e}},k=class{constructor(e=[0,0,0]){m(this,`items`,void 0),m(this,`range`,void 0),this.items=[],this.range=e}},ke=class{constructor(){m(this,`lineStarts`,[0])}addNewLine(e){e>this.lineStarts[this.lineStarts.length-1]&&this.lineStarts.push(e)}linePos(e){let t=0,n=this.lineStarts.length-1;for(;t<n;){let r=t+n+1>>1;this.lineStarts[r]<=e?t=r:n=r-1}return{line:t+1,col:e-this.lineStarts[t]+1}}},A=class extends Error{constructor(e,t){super(e),m(this,`pos`,void 0),this.pos=t}};function Ae(e,t={}){let n=t.lineCounter,r=[];if(n)for(let t=0;t<e.length;t++)e[t]===`
2
2
  `&&n.addNewLine(t+1);try{return{contents:new je(e,r).parseRoot(),errors:r}}catch(e){if(e instanceof A)return r.push(e),{contents:null,errors:r};throw e}}var je=class{constructor(e,t){m(this,`lines`,void 0),m(this,`pos`,void 0),m(this,`text`,void 0),m(this,`errors`,void 0),this.text=e,this.errors=t,this.lines=[],this.pos=0;let n=0,r=e.split(`
3
3
  `);for(let e of r){let t=e.replace(/\r$/,``),r=t.replace(/^\s+/,``),i=t.length-r.length;this.lines.push({indent:i,content:r,offset:n+i,lineOffset:n,raw:t}),n+=e.length+1}for(;this.lines.length>0&&this.lines[this.lines.length-1].content===``;)this.lines.pop()}parseRoot(){if(this.lines.length===0)return null;let e=this.parseNode(0);if(this.skipEmpty(),this.pos<this.lines.length){let e=this.currentLine();this.addError(`Unexpected scalar at node end`,e.offset)}return e}currentLine(){return this.lines[this.pos]}parseNode(e){this.skipEmpty();let t=this.currentLine();return!t||t.indent<e?null:t.content.startsWith(`- `)||t.content===`-`?this.parseSequence(t.indent):this.isMapEntry(t.content)?this.parseMap(t.indent):(this.pos++,this.parseScalarValue(t.content,t.offset,t))}parseSequence(e){let t=this.currentLine(),n=new k([t.offset-t.indent,0,0]),r=t.offset;for(;this.pos<this.lines.length;){this.skipEmpty();let t=this.currentLine();if(!t||t.indent<e)break;if(t.indent>e){this.addError(`Bad indentation of a sequence entry`,t.offset);break}if(!t.content.startsWith(`- `)&&t.content!==`-`)break;let i=t.content===`-`?1:2,a=t.content.slice(i),o=t.offset+i;if(this.pos++,a===``||a.trim()===``){let t=this.parseNode(e+1);t&&(n.items.push(t),r=this.peekLastOffset())}else if(this.isMapEntry(a)){let s=this.parseInlineMap(a,o,e+i,t);n.items.push(s),r=s.range[2]}else{let e=this.parseScalarValue(a,o,t);n.items.push(e),r=e.range[2]}}return n.range[1]=r,n.range[2]=r,n}parseMap(e){let t=this.currentLine(),n=new O([t.offset-t.indent,0,0]),r=t.offset;for(;this.pos<this.lines.length;){this.skipEmpty();let t=this.currentLine();if(!t||t.indent<e)break;if(t.indent>e){this.addError(`Bad indentation of a mapping entry`,t.offset);break}if(!this.isMapEntry(t.content))break;let{key:i,valueStr:a,colonOffset:o,valueOffset:s}=this.splitMapEntry(t.content,t.offset),c=new D(i,[t.offset,t.offset+i.length,o]);this.pos++;let l;l=a===``?this.parseMapValue(e,o):this.parseScalarValue(a.trim(),s,t),n.items.push({key:c,value:l}),r=l?this.getNodeEnd(l):o+1}return n.range[1]=r,n.range[2]=r,n}parseInlineMap(e,t,n,r){let i=new O([t,0,0]),a=t,{key:o,valueStr:s,colonOffset:c,valueOffset:l}=this.splitMapEntry(e,t),u=new D(o,[t,t+o.length,c]),d;for(d=s===``?this.parseMapValue(n,c):this.parseScalarValue(s.trim(),l,r),i.items.push({key:u,value:d}),a=d?this.getNodeEnd(d):c+1;this.pos<this.lines.length;){this.skipEmpty();let e=this.currentLine();if(!e||e.indent<n||e.indent>n||!this.isMapEntry(e.content)||e.content.startsWith(`- `))break;let t=this.splitMapEntry(e.content,e.offset),r=new D(t.key,[e.offset,e.offset+t.key.length,t.colonOffset]);this.pos++;let o;o=t.valueStr===``?this.parseMapValue(n,t.colonOffset):this.parseScalarValue(t.valueStr.trim(),t.valueOffset,e),i.items.push({key:r,value:o}),a=o?this.getNodeEnd(o):t.colonOffset+1}return i.range[1]=a,i.range[2]=a,i}parseMapValue(e,t){this.skipEmpty();let n=this.currentLine();return!n||n.indent<e?new D(null,[t+1,t+1,t+1]):n.indent===e&&(n.content.startsWith(`- `)||n.content===`-`)?this.parseNode(e):n.indent>e?this.parseNode(e+1):new D(null,[t+1,t+1,t+1])}getNodeEnd(e){return e.range[2]}peekLastOffset(){if(this.pos>0&&this.pos<=this.lines.length){let e=this.lines[this.pos-1];return e.lineOffset+e.raw.length}return this.text.length}parseScalarValue(e,t,n){let r=e.trim(),i=t+e.indexOf(r),a=i+r.length,o=n.lineOffset+n.raw.length;return r.startsWith(`"`)?this.parseQuotedScalar(r,i,o):r===`true`||r===`false`?new D(r===`true`,[i,a,o]):r===`null`||r===`~`?new D(null,[i,a,o]):r!==``&&Me(r)?new D(Number(r),[i,a,o]):new D(r,[i,a,o])}parseQuotedScalar(e,t,n){let r=``,i=1;for(;i<e.length;){let a=e[i];if(a===`\\`){if(i++,i>=e.length){this.addError(`Unterminated double-quoted string`,t+i);break}let n=e[i];switch(n){case`n`:r+=`
4
4
  `;break;case`t`:r+=` `;break;case`r`:r+=`\r`;break;case`"`:r+=`"`;break;case`\\`:r+=`\\`;break;case`/`:r+=`/`;break;default:r+=n}}else if(a===`"`){let e=t+i+1;return new D(r,[t,e,n])}else r+=a;i++}return this.addError(`Unterminated double-quoted string`,t),new D(r,[t,t+e.length,n])}skipEmpty(){for(;this.pos<this.lines.length&&this.lines[this.pos].content===``;)this.pos++}isMapEntry(e){return this.findMapColon(e)>=0}findMapColon(e){let t=!1,n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(n){n=!1;continue}if(i===`\\`&&t){n=!0;continue}if(i===`"`){t=!t;continue}if(!t&&i===`:`&&(r+1>=e.length||e[r+1]===` `))return r}return-1}splitMapEntry(e,t){let n=this.findMapColon(e),r=e.slice(0,n).trim(),i=t+n,a=e.slice(n+1),o=a.trimStart(),s=i+1+(a.length-a.trimStart().length);return r.startsWith(`"`)&&r.endsWith(`"`)?{key:r.slice(1,-1),valueStr:o,colonOffset:i,valueOffset:s}:{key:r,valueStr:o,colonOffset:i,valueOffset:s}}addError(e,t){this.errors.push(new A(e,[t,t+1]))}};function Me(e){return e===``||e===`-`||e===`+`?!1:/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(e)}function j(e){return Ne(e)?`"`+e.replace(/[\\"\x00-\x1f\x7f-\x9f]/g,e=>{switch(e){case`\\`:return`\\\\`;case`"`:return`\\"`;case`\b`:return`\\b`;case`\f`:return`\\f`;case`
@@ -7,7 +7,7 @@ import{Snapshots as e,recordArtifact as t,expect as n,chai as r}from"vitest";imp
7
7
  `)}function Ve(e){let t=e.role;return e.name!==void 0&&(t+=` ${ze(e.name)}`),t+=N(e),t}function I(e,t,n){if(e.kind===`text`){n.push(`${t}- text: ${F(e.text)}`);return}let r=Ve(e),i=e.children||[],a=[];if(e.containerMode&&e.containerMode!==`contain`&&a.push(`${t} - /children: ${e.containerMode}`),e.props)for(let[n,r]of Object.entries(e.props))a.push(`${t} - /${n}: ${F(r)}`);if(i.length===0&&a.length===0){n.push(`${t}- ${r}`);return}if(i.length===1&&i[0].kind===`text`&&a.length===0){n.push(`${t}- ${r}: ${F(i[0].text)}`);return}n.push(`${t}- ${r}:`),n.push(...a);for(let e of i)I(e,`${t} `,n)}function He(e,t){let n=e.role===`fragment`?e.children:[e],r=L(t)?t.children??[]:[t],i=L(t)?t.containerMode:void 0,a=qe(n,r,``,i);return a.pass&&i&&i!==`contain`&&a.resolved.unshift(`- /children: ${i}`),{pass:a.pass,resolved:a.resolved.join(`
8
8
  `)}}function L(e){return e.kind===`role`&&e.role===`fragment`}function Ue(e){return typeof e==`object`&&!!e&&`pattern`in e}function We(e,t){let n=e.role;return t.name===void 0||(Ue(t.name)&&b(e.name,t.name)?n+=` ${ze(t.name)}`:e.name&&(n+=` ${JSON.stringify(e.name)}`)),t.level!==void 0&&(n+=` [level=${e.level}]`),t.checked!==void 0&&(e.checked===!0?n+=` [checked]`:e.checked===`mixed`&&(n+=` [checked=mixed]`)),t.disabled!==void 0&&e.disabled&&(n+=` [disabled]`),t.expanded!==void 0&&(e.expanded===!0?n+=` [expanded]`:e.expanded===!1&&(n+=` [expanded=false]`)),t.pressed!==void 0&&(e.pressed===!0?n+=` [pressed]`:e.pressed===`mixed`&&(n+=` [pressed=mixed]`)),t.selected!==void 0&&e.selected&&(n+=` [selected]`),n}function Ge(e,t){let n=new Map,r=0;for(let i=0;i<e.length&&r<t.length;i++)w(e[i],t[r],!1)&&(n.set(i,r),r++);return n}function Ke(e,t){let n=new Map,r=new Set;for(let i=0;i<t.length;i++)for(let a=0;a<e.length;a++)if(!r.has(a)&&w(e[a],t[i],!1)){n.set(a,i),r.add(a);break}return n}function R(e,t){let n=[];return typeof e==`string`?n.push(`${t}- text: ${e}`):P(e,t,n),n}function qe(e,t,n,r){if(e.some(e=>typeof e!=`string`&&e.role===`fragment`)||t.some(e=>L(e)))throw Error(`Internal error: fragment wrappers must be unwrapped at matchAriaTree root`);if(r===`equal`||r===`deep-equal`)return Je(e,t,n,r===`deep-equal`);let i=[],a=Ge(e,t);if(t.length!==a.size){let r=Ke(e,t);for(let a=0;a<e.length;a++){let o=r.get(a);o===void 0?i.push(...R(e[a],n)):i.push(...z(e[a],t[o],n))}return{resolved:i,pass:!1}}for(let r=0;r<e.length;r++){let o=a.get(r);o!==void 0&&i.push(...z(e[r],t[o],n))}return{resolved:i,pass:!0}}function Je(e,t,n,r){let i=[],a=e.length===t.length&&e.every((e,n)=>w(e,t[n],r));for(let a=0;a<e.length;a++)a<t.length?i.push(...z(e[a],t[a],n,r)):i.push(...R(e[a],n));return{resolved:i,pass:a}}function z(e,t,n,r=!1){if(typeof e==`string`&&t.kind===`text`)return[`${n}- text: ${x(e,t.text)&&C(t.text)?F(t.text):e}`];if(typeof e==`string`||t.kind===`text`)return R(e,n);let i=We(e,t),a=qe(e.children,t.children||[],`${n} `,t.containerMode??(r?`equal`:`contain`)),o=[];t.containerMode&&t.containerMode!==`contain`&&o.push(`${n} - /children: ${t.containerMode}`);let s=new Set([...Object.keys(e.props),...Object.keys(t.props||{})]);for(let r of s){let i=e.props[r],a=t.props?.[r];if(i!==void 0){let e=(a===void 0||x(i,a))&&a&&C(a)?F(a):i;o.push(`${n} - /${r}: ${e}`)}}let c=[];if(!a.resolved.length&&!o.length)c.push(`${n}- ${i}`);else if(a.resolved.length===1&&a.resolved[0].trimStart().startsWith(`- text: `)&&!o.length){let e=a.resolved[0].trimStart().slice(8);c.push(`${n}- ${i}: ${e}`)}else c.push(`${n}- ${i}:`),c.push(...o),c.push(...a.resolved);return c}function Ye(e){return we(Oe,e)}var Xe=Object.freeze({__proto__:null,generateAriaTree:Pe,matchAriaTree:He,parseAriaTemplate:Ye,renderAriaTemplate:Be,renderAriaTree:Re});h().aria=Xe;const{generateAriaTree:Ze,matchAriaTree:Qe,parseAriaTemplate:$e,renderAriaTemplate:et,renderAriaTree:tt}=Xe,B={name:`aria`,capture(e){if(e instanceof Element)return Ze(e);throw TypeError(`aria adapter expects an Element`)},render(e){return V(tt(e))},parseExpected(e){let t=Error.stackTraceLimit;Error.stackTraceLimit=t+20;try{return $e(e.trim())}finally{Error.stackTraceLimit=t}},match(e,t){let n=Qe(e,t);return n.pass?{pass:!0}:{pass:!1,message:`Accessibility tree does not match expected template`,resolved:V(n.resolved),expected:V(et(t))}}};function V(e){return`\n${e}\n`}const nt={toMatchAriaSnapshot(t){return e.toMatchDomainSnapshot.call(this,B,t)},toMatchAriaInlineSnapshot(t,n){return e.toMatchDomainInlineSnapshot.call(this,B,t,n)}};for(let e of Object.values(nt))Object.assign(e,{__vitest_poll_takeover__:!0});function rt(){return[...d]}function it(e,t,n){return e instanceof g&&(e=e.query()),e==null?null:H(e,t,n)}function H(e,t,n){e instanceof g&&(e=e.element());let r=e?.ownerDocument?.defaultView||window;if(e instanceof r.HTMLElement||e instanceof r.SVGElement)return e;throw new ft(e,t,n)}function at(e,t,n){e instanceof g&&(e=e.element());let r=e.ownerDocument?.defaultView||window;if(e instanceof r.Node)return e;throw new pt(e,t,n)}function U(e,t,n,r,i,a){return[`${t}\n`,`${n}:\n${e.utils.EXPECTED_COLOR(W(lt(e,r),2))}`,`${i}:\n${e.utils.RECEIVED_COLOR(W(lt(e,a),2))}`].join(`
9
9
  `)}function W(e,t){return ot(ct(e),t)}function ot(e,t){return e.replace(/^(?!\s*$)/gm,` `.repeat(t))}function st(e){let t=e.match(/^[ \t]*(?=\S)/gm);return t?t.reduce((e,t)=>Math.min(e,t.length),1/0):0}function ct(e){let t=st(e);if(t===0)return e;let n=RegExp(`^[ \\t]{${t}}`,`gm`);return e.replace(n,``)}function lt(e,t){return typeof t==`string`?t:e.utils.stringify(t)}function ut(e,{wordConnector:t=`, `,lastWordConnector:n=` and `}={}){return[e.slice(0,-1).join(t),e.at(-1)].join(e.length>1?n:``)}class dt extends Error{constructor(e,t,n,r){super(),Error.captureStackTrace&&Error.captureStackTrace(this,n);let i=``;try{i=r.utils.printWithType(`Received`,t,r.utils.printReceived)}catch{}this.message=[r.utils.matcherHint(`${r.isNot?`.not`:``}.${n.name}`,`received`,``),``,`${r.utils.RECEIVED_COLOR(`received`)} value must ${e} or a Locator that returns ${e}.`,i].join(`
10
- `)}}class ft extends dt{constructor(e,t,n){super(`an HTMLElement or an SVGElement`,e,t,n)}}class pt extends dt{constructor(e,t,n){super(`a Node`,e,t,n)}}function G(e){return e instanceof HTMLFormElement?`FORM`:e.tagName.toUpperCase()}function K(e){return G(e)===`INPUT`}function mt(e){if(e)switch(G(e)){case`INPUT`:return gt(e);case`SELECT`:return ht(e);default:return e.value??vt(e)}}function ht({multiple:e,options:t}){let n=[...t].filter(e=>e.selected);if(e)return[...n].map(e=>e.value);if(n.length!==0)return n[0].value}function gt(e){switch(e.type){case`number`:return e.value===``?null:Number(e.value);case`checkbox`:return e.checked;default:return e.value}}const _t=[`meter`,`progressbar`,`slider`,`spinbutton`];function vt(e){if(_t.includes(e.getAttribute(`role`)||``))return Number(e.getAttribute(`aria-valuenow`))}function yt(e){return e.replace(/\s+/g,` `).trim()}function bt(e,t){return t instanceof RegExp?t.test(e):e.includes(String(t))}function q(e,t){if(Array.isArray(e)&&Array.isArray(t)){let n=new Set(t);for(let t of new Set(e))if(!n.has(t))return!1;return!0}}const xt=rt();function St(e){let t=H(e,St,this);if(!(K(t)&&[`checkbox`,`radio`].includes(t.type))&&!(xt.includes(c(t)||``)&&[`true`,`false`].includes(t.getAttribute(`aria-checked`)||``)))return{pass:!1,message:()=>`only inputs with type="checkbox" or type="radio" or elements with ${Ct()} and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead`};let n=f(t)===!0;return{pass:n,message:()=>{let e=n?`is`:`is not`;return[this.utils.matcherHint(`${this.isNot?`.not`:``}.toBeChecked`,`element`,``),``,`Received element ${e} checked:`,` ${this.utils.printReceived(t.cloneNode(!1))}`].join(`
10
+ `)}}class ft extends dt{constructor(e,t,n){super(`an HTMLElement or an SVGElement`,e,t,n)}}class pt extends dt{constructor(e,t,n){super(`a Node`,e,t,n)}}function G(e){return e instanceof HTMLFormElement?`FORM`:e.tagName.toUpperCase()}function K(e){return G(e)===`INPUT`}function mt(e){if(e)switch(G(e)){case`INPUT`:return gt(e);case`SELECT`:return ht(e);default:return e.value??vt(e)}}function ht({multiple:e,options:t}){let n=[...t].filter(e=>e.selected);if(e)return Array.from(n,e=>e.value);if(n.length!==0)return n[0].value}function gt(e){switch(e.type){case`number`:return e.value===``?null:Number(e.value);case`checkbox`:return e.checked;default:return e.value}}const _t=[`meter`,`progressbar`,`slider`,`spinbutton`];function vt(e){if(_t.includes(e.getAttribute(`role`)||``))return Number(e.getAttribute(`aria-valuenow`))}function yt(e){return e.replace(/\s+/g,` `).trim()}function bt(e,t){return t instanceof RegExp?t.test(e):e.includes(String(t))}function q(e,t){if(Array.isArray(e)&&Array.isArray(t)){let n=new Set(t);for(let t of new Set(e))if(!n.has(t))return!1;return!0}}const xt=rt();function St(e){let t=H(e,St,this);if(!(K(t)&&[`checkbox`,`radio`].includes(t.type))&&!(xt.includes(c(t)||``)&&[`true`,`false`].includes(t.getAttribute(`aria-checked`)||``)))return{pass:!1,message:()=>`only inputs with type="checkbox" or type="radio" or elements with ${Ct()} and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead`};let n=f(t)===!0;return{pass:n,message:()=>{let e=n?`is`:`is not`;return[this.utils.matcherHint(`${this.isNot?`.not`:``}.toBeChecked`,`element`,``),``,`Received element ${e} checked:`,` ${this.utils.printReceived(t.cloneNode(!1))}`].join(`
11
11
  `)}}}function Ct(){return ut(xt.map(e=>`role="${e}"`),{lastWordConnector:` or `})}function wt(e){let t=H(e,wt,this);return{pass:Tt(t),message:()=>[this.utils.matcherHint(`${this.isNot?`.not`:``}.toBeEmptyDOMElement`,`element`,``),``,`Received:`,` ${this.utils.printReceived(t.innerHTML)}`].join(`
12
12
  `)}}function Tt(e){return[...e.childNodes].filter(e=>e.nodeType!==Node.COMMENT_NODE).length===0}function Et(e){let t=H(e,Et,this),n=Ot(t);return{pass:n,message:()=>{let e=n?`is`:`is not`;return[this.utils.matcherHint(`${this.isNot?`.not`:``}.toBeDisabled`,`element`,``),``,`Received element ${e} disabled:`,` ${this.utils.printReceived(t.cloneNode(!1))}`].join(`
13
13
  `)}}}function Dt(e){let t=H(e,Dt,this),n=Ot(t);return{pass:!n,message:()=>{let e=n?`is not`:`is`;return[this.utils.matcherHint(`${this.isNot?`.not`:``}.toBeEnabled`,`element`,``),``,`Received element ${e} enabled:`,` ${this.utils.printReceived(t.cloneNode(!1))}`].join(`
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ResolvedConfig, BrowserServerFactory, BrowserCommand, BrowserProviderOption } from 'vitest/node';
1
+ import { TestProject, ResolvedConfig, BrowserServerFactory, BrowserCommand, BrowserProviderOption } from 'vitest/node';
2
2
  import { ScreenshotMatcherOptions, NonStandardScreenshotComparators, ScreenshotComparatorRegistry } from '@vitest/browser/context';
3
3
 
4
4
  interface BaseMetadata {
@@ -61,6 +61,8 @@ declare function parseKeyDef(text: string): {
61
61
  repeat: number;
62
62
  }[];
63
63
  declare function resolveScreenshotPath(testPath: string, name: string, config: ResolvedConfig, customPath: string | undefined): string;
64
+ declare function assertBrowserFileAccess(project: TestProject, path: string): void;
65
+ declare function assertBrowserApiWrite(project: TestProject, path: string): void;
64
66
 
65
67
  interface SerializedLocator {
66
68
  selector: string;
@@ -73,5 +75,5 @@ declare function defineBrowserProvider<T extends object = object>(options: Omit<
73
75
  options?: T;
74
76
  }): BrowserProviderOption;
75
77
 
76
- export { createBrowserServer, defineBrowserCommand, defineBrowserProvider, parseKeyDef, resolveScreenshotPath };
78
+ export { assertBrowserApiWrite, assertBrowserFileAccess, createBrowserServer, defineBrowserCommand, defineBrowserProvider, parseKeyDef, resolveScreenshotPath };
77
79
  export type { CustomComparatorsRegistry, SerializedLocator };
package/dist/index.js CHANGED
@@ -1,24 +1,26 @@
1
1
  import { ManualMockedModule, RedirectedModule, AutomockedModule, AutospiedModule, MockerRegistry } from '@vitest/mocker';
2
2
  import { dynamicImportPlugin, ServerMockResolver, interceptorPlugin } from '@vitest/mocker/node';
3
3
  import c from 'tinyrainbow';
4
- import { isValidApiRequest, isFileServingAllowed, distDir, rolldownVersion, resolveApiServerConfig, resolveFsAllow, isFileLoadingAllowed, createDebugger, createViteLogger, createViteServer } from 'vitest/node';
4
+ import { isFileLoadingAllowed, isValidApiRequest, isFileServingAllowed, distDir, rolldownVersion, resolveApiServerConfig, resolveFsAllow, createDebugger, createViteLogger, createViteServer } from 'vitest/node';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import fs, { readFileSync, createReadStream, promises, existsSync } from 'node:fs';
7
7
  import { createRequire } from 'node:module';
8
+ import { distClientRoot } from '@vitest/ui';
8
9
  import { slash as slash$1, toArray, deepMerge } from '@vitest/utils/helpers';
9
10
  import MagicString from 'magic-string';
10
11
  import sirv from 'sirv';
11
12
  import crypto from 'node:crypto';
13
+ import { platform } from 'node:os';
14
+ import 'vite';
12
15
  import { readFile as readFile$1, mkdir, writeFile as writeFile$1 } from 'node:fs/promises';
13
16
  import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
14
17
  import { extractSourcemapFromFile } from '@vitest/utils/source-map/node';
15
18
  import { resolve as resolve$1, basename as basename$1, dirname as dirname$1 } from 'node:path';
16
- import { platform } from 'node:os';
17
19
  import { PNG } from 'pngjs';
18
20
  import { diff } from '@blazediff/core';
19
21
  import { WebSocketServer } from 'ws';
20
22
 
21
- var version = "5.0.0-beta.4";
23
+ var version = "5.0.0-beta.6";
22
24
 
23
25
  const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
24
26
  function normalizeWindowsPath(input = "") {
@@ -213,6 +215,8 @@ const basename = function(p, extension) {
213
215
  const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
214
216
  const distRoot = resolve(pkgRoot, "dist");
215
217
 
218
+ const API_TOKEN_FILE = ".vitest-secret-token";
219
+
216
220
  /// <reference types="../types/index.d.ts" />
217
221
 
218
222
  // (c) 2020-present Andrea Giammarchi
@@ -674,23 +678,26 @@ async function getBrowserProvider(options, project) {
674
678
  function slash(path) {
675
679
  return path.replace(/\\/g, "/").replace(/\/+/g, "/");
676
680
  }
681
+ function assertBrowserFileAccess(project, path) {
682
+ const normalized = slash(path);
683
+ if (!isFileLoadingAllowed(project.vite.config, normalized) && !isFileLoadingAllowed(project.vitest.vite.config, normalized)) {
684
+ throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
685
+ }
686
+ }
687
+ function assertBrowserApiWrite(project, path) {
688
+ if (!project.config.browser.api.allowWrite || !project.vitest.config.api.allowWrite) {
689
+ throw new Error(`Cannot modify file "${path}". File writing is disabled because the server is exposed to the internet, see https://vitest.dev/config/browser/api.`);
690
+ }
691
+ }
677
692
 
678
693
  async function resolveOrchestrator(globalServer, url, res) {
679
- let sessionId = url.searchParams.get("sessionId");
680
- // it's possible to open the page without a context
681
- if (!sessionId) {
682
- const contexts = [...globalServer.children].flatMap((p) => [...p.state.orchestrators.keys()]);
683
- sessionId = contexts.at(-1) ?? "none";
684
- }
685
- // it's ok to not have a session here, especially in the preview provider
686
- // because the user could refresh the page which would remove the session id from the url
687
- const session = globalServer.vitest._browserSessions.getSession(sessionId);
688
- const browserProject = session?.project.browser || [...globalServer.children][0];
689
- if (!browserProject) {
694
+ const sessionId = url.searchParams.get("sessionId");
695
+ const session = sessionId && globalServer.vitest._browserSessions.getSession(sessionId);
696
+ if (!session) {
690
697
  return;
691
698
  }
692
- // ignore unknown pages
693
- if (sessionId && sessionId !== "none" && !globalServer.vitest._browserSessions.sessionIds.has(sessionId)) {
699
+ const browserProject = session.project.browser;
700
+ if (!browserProject) {
694
701
  return;
695
702
  }
696
703
  const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
@@ -768,7 +775,10 @@ function createOrchestratorMiddleware(parentServer) {
768
775
  allowIframes(res);
769
776
  res.write(html, "utf-8");
770
777
  res.end();
778
+ return;
771
779
  }
780
+ res.statusCode = 404;
781
+ res.end("Not found");
772
782
  };
773
783
  }
774
784
 
@@ -779,8 +789,8 @@ async function resolveTester(globalServer, url, res, next) {
779
789
  // but keep the rest of the CSP
780
790
  res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
781
791
  }
782
- const sessionId = url.searchParams.get("sessionId") || "none";
783
- const session = globalServer.vitest._browserSessions.getSession(sessionId);
792
+ const sessionId = url.searchParams.get("sessionId");
793
+ const session = sessionId && globalServer.vitest._browserSessions.getSession(sessionId);
784
794
  if (!session) {
785
795
  res.statusCode = 400;
786
796
  res.end("Invalid session ID");
@@ -1063,7 +1073,6 @@ var BrowserPlugin = (parentServer, base = "/") => {
1063
1073
  "@vitest/browser/client",
1064
1074
  "@vitest/utils",
1065
1075
  "@vitest/utils/source-map",
1066
- "@vitest/runner",
1067
1076
  "@vitest/spy",
1068
1077
  "@vitest/utils/error",
1069
1078
  "std-env",
@@ -1162,7 +1171,7 @@ var BrowserPlugin = (parentServer, base = "/") => {
1162
1171
  {
1163
1172
  name: "vitest:browser:assets",
1164
1173
  configureServer(server) {
1165
- server.middlewares.use("/__vitest__", sirv(resolve(distRoot, "client/__vitest__")));
1174
+ server.middlewares.use("/__vitest__", sirv(distClientRoot));
1166
1175
  },
1167
1176
  resolveId(id) {
1168
1177
  if (id.startsWith("/__vitest_browser__/")) {
@@ -1212,6 +1221,8 @@ var BrowserPlugin = (parentServer, base = "/") => {
1212
1221
  };
1213
1222
  viteConfig.server.fs ??= {};
1214
1223
  viteConfig.server.fs.allow = viteConfig.server.fs.allow || [];
1224
+ viteConfig.server.fs.deny ??= [];
1225
+ viteConfig.server.fs.deny.push(API_TOKEN_FILE);
1215
1226
  viteConfig.server.fs.allow.push(...resolveFsAllow(parentServer.vitest.config.root, parentServer.vitest.vite.config.configFile), distRoot);
1216
1227
  return { resolve: { alias: viteConfig.test?.alias } };
1217
1228
  }
@@ -1909,19 +1920,9 @@ _Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _M
1909
1920
 
1910
1921
  var mime = new Mime(types)._freeze();
1911
1922
 
1912
- function assertFileAccess(path, project) {
1913
- if (!isFileLoadingAllowed(project.vite.config, path) && !isFileLoadingAllowed(project.vitest.vite.config, path)) {
1914
- throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
1915
- }
1916
- }
1917
- function assertWrite(path, project) {
1918
- if (!project.config.browser.api.allowWrite || !project.vitest.config.api.allowWrite) {
1919
- throw new Error(`Cannot modify file "${path}". File writing is disabled because server is exposed to the internet, see https://vitest.dev/config/browser/api.`);
1920
- }
1921
- }
1922
1923
  const readFile = async ({ project }, path, options = {}) => {
1923
1924
  const filepath = resolve$1(project.config.root, path);
1924
- assertFileAccess(slash(filepath), project);
1925
+ assertBrowserFileAccess(project, filepath);
1925
1926
  // never return a Buffer
1926
1927
  if (typeof options === "object" && !options.encoding) {
1927
1928
  options.encoding = "utf-8";
@@ -1929,9 +1930,9 @@ const readFile = async ({ project }, path, options = {}) => {
1929
1930
  return promises.readFile(filepath, options);
1930
1931
  };
1931
1932
  const writeFile = async ({ project }, path, data, options) => {
1932
- assertWrite(path, project);
1933
+ assertBrowserApiWrite(project, path);
1933
1934
  const filepath = resolve$1(project.config.root, path);
1934
- assertFileAccess(slash(filepath), project);
1935
+ assertBrowserFileAccess(project, filepath);
1935
1936
  const dir = dirname$1(filepath);
1936
1937
  if (!fs.existsSync(dir)) {
1937
1938
  await promises.mkdir(dir, { recursive: true });
@@ -1939,14 +1940,14 @@ const writeFile = async ({ project }, path, data, options) => {
1939
1940
  await promises.writeFile(filepath, data, options);
1940
1941
  };
1941
1942
  const removeFile = async ({ project }, path) => {
1942
- assertWrite(path, project);
1943
+ assertBrowserApiWrite(project, path);
1943
1944
  const filepath = resolve$1(project.config.root, path);
1944
- assertFileAccess(slash(filepath), project);
1945
+ assertBrowserFileAccess(project, filepath);
1945
1946
  await promises.rm(filepath);
1946
1947
  };
1947
1948
  const _fileInfo = async ({ project }, path, encoding) => {
1948
1949
  const filepath = resolve$1(project.config.root, path);
1949
- assertFileAccess(slash(filepath), project);
1950
+ assertBrowserFileAccess(project, filepath);
1950
1951
  const content = await promises.readFile(filepath, encoding || "base64");
1951
1952
  return {
1952
1953
  content,
@@ -2111,7 +2112,7 @@ function resolveOptions({ context, name, options, testName }) {
2111
2112
  ext: `.${extension}`,
2112
2113
  platform: platform(),
2113
2114
  root,
2114
- screenshotDirectory: relative(root, join(root, context.project.config.browser.screenshotDirectory ?? "__screenshots__")),
2115
+ screenshotDirectory: context.project.config.browser.expect?.toMatchScreenshot?.screenshotDirectory ?? "__screenshots__",
2115
2116
  attachmentsDir: relative(root, context.project.config.attachmentsDir),
2116
2117
  testFileDirectory: relative(root, dirname(context.testPath)),
2117
2118
  testFileName: basename(context.testPath),
@@ -2218,7 +2219,7 @@ function asyncTimeout(timeout) {
2218
2219
  if (timeout === 0) {
2219
2220
  resolve(null);
2220
2221
  } else {
2221
- setTimeout(() => resolve(null), timeout);
2222
+ setTimeout(resolve, timeout, null);
2222
2223
  }
2223
2224
  });
2224
2225
  }
@@ -2292,7 +2293,7 @@ const screenshotMatcher = async (context, name, testName, options) => {
2292
2293
  comparator,
2293
2294
  comparatorOptions
2294
2295
  });
2295
- await performSideEffects(outcome, codec);
2296
+ await performSideEffects(outcome, codec, context.project);
2296
2297
  return buildOutput(outcome, timeout);
2297
2298
  };
2298
2299
  /**
@@ -2382,17 +2383,17 @@ async function determineOutcome({ comparator, comparatorOptions, paths, referenc
2382
2383
  *
2383
2384
  * Only `missing-reference`, `update-reference`, and `mismatch` write files. Successful matches produce no side effects.
2384
2385
  */
2385
- async function performSideEffects(outcome, codec) {
2386
+ async function performSideEffects(outcome, codec, project) {
2386
2387
  switch (outcome.type) {
2387
2388
  case "missing-reference":
2388
2389
  case "update-reference": {
2389
- await writeScreenshot(outcome.reference.path, await encodeScreenshot(outcome.reference, codec));
2390
+ await writeScreenshot(outcome.reference.path, await encodeScreenshot(outcome.reference, codec), project);
2390
2391
  break;
2391
2392
  }
2392
2393
  case "mismatch": {
2393
- await writeScreenshot(outcome.actual.path, await encodeScreenshot(outcome.actual, codec));
2394
+ await writeScreenshot(outcome.actual.path, await encodeScreenshot(outcome.actual, codec), project);
2394
2395
  if (outcome.diff) {
2395
- await writeScreenshot(outcome.diff.path, await codec.encode(outcome.diff.image, {}));
2396
+ await writeScreenshot(outcome.diff.path, await codec.encode(outcome.diff.image, {}), project);
2396
2397
  }
2397
2398
  break;
2398
2399
  }
@@ -2554,8 +2555,10 @@ async function takeScreenshotData({ buffer, codec, context, element, name, scree
2554
2555
  };
2555
2556
  }
2556
2557
  /** Writes encoded images to disk, creating parent directories as needed. */
2557
- async function writeScreenshot(path, image) {
2558
+ async function writeScreenshot(path, image, project) {
2558
2559
  try {
2560
+ assertBrowserApiWrite(project, path);
2561
+ assertBrowserFileAccess(project, path);
2559
2562
  await mkdir(dirname(path), { recursive: true });
2560
2563
  await writeFile$1(path, image);
2561
2564
  } catch (cause) {
@@ -2780,7 +2783,7 @@ class ParentBrowserProject {
2780
2783
  this.manifest = (async () => {
2781
2784
  return JSON.parse(await readFile$1(`${distRoot}/client/.vite/manifest.json`, "utf8"));
2782
2785
  })().then((manifest) => this.manifest = manifest);
2783
- this.orchestratorHtml = (project.config.browser.ui ? readFile$1(resolve(distRoot, "client/__vitest__/index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8")).then((html) => this.orchestratorHtml = html);
2786
+ this.orchestratorHtml = (project.config.browser.ui ? readFile$1(resolve(distClientRoot, "index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8")).then((html) => this.orchestratorHtml = html);
2784
2787
  this.injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8").then((js) => this.injectorJs = js);
2785
2788
  this.errorCatcherUrl = join("/@fs/", resolve(distRoot, "client/error-catcher.js"));
2786
2789
  this.matchersUrl = join("/@fs/", distRoot, "expect-element.js");
@@ -3119,7 +3122,8 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
3119
3122
  }
3120
3123
  if (type === "orchestrator") {
3121
3124
  const session = sessions.getSession(sessionId);
3122
- // it's possible the session was already resolved by the preview provider
3125
+ // it's possible the session was already resolved by the preview provider,
3126
+ // but we still mark the websocket connection when the page reconnects
3123
3127
  session?.connected();
3124
3128
  }
3125
3129
  const project = vitest.getProjectByName(projectName);
@@ -3128,7 +3132,7 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
3128
3132
  }
3129
3133
  wss.handleUpgrade(request, socket, head, (ws) => {
3130
3134
  wss.emit("connection", ws, request);
3131
- const { rpc, offCancel } = setupClient(project, rpcId, ws);
3135
+ const { rpc, offCancel } = setupClient(project, rpcId, ws, { sessionId });
3132
3136
  const state = project.browser.state;
3133
3137
  const clients = type === "tester" ? state.testers : state.orchestrators;
3134
3138
  clients.set(rpcId, rpc);
@@ -3167,10 +3171,14 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
3167
3171
  throw new Error(`Cannot use CDP because browser API write or exec operations are disabled. See https://vitest.dev/config/browser/api.`);
3168
3172
  }
3169
3173
  }
3170
- function setupClient(project, rpcId, ws) {
3174
+ function setupClient(project, rpcId, ws, options) {
3171
3175
  const mockResolver = new ServerMockResolver(globalServer.vite, { moduleDirectories: project.config?.deps?.moduleDirectories });
3172
3176
  const mocker = project.browser?.provider.mocker;
3173
3177
  const rpc = createBirpc({
3178
+ onOrchestratorReady() {
3179
+ const sessions = vitest._browserSessions;
3180
+ sessions.getSession(options.sessionId)?.ready();
3181
+ },
3174
3182
  async onUnhandledError(error, type) {
3175
3183
  if (error && typeof error === "object") {
3176
3184
  const _error = error;
@@ -3204,6 +3212,16 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
3204
3212
  artifact.attachments = [];
3205
3213
  vitest.logger.error(`[vitest] Cannot record attachments ("${attachments}") because file writing is disabled, removing attachments from artifact "${artifact.type}". See https://vitest.dev/config/browser/api.`);
3206
3214
  }
3215
+ } else {
3216
+ // attachment files are copied into `attachmentsDir`, so confine
3217
+ // client-supplied paths to Vite's `server.fs` boundary
3218
+ const attachments = artifact.type === "internal:annotation" ? artifact.annotation.attachment ? [artifact.annotation.attachment] : [] : artifact.attachments ?? [];
3219
+ for (const attachment of attachments) {
3220
+ const path = attachment.path;
3221
+ if (path && !path.startsWith("http://") && !path.startsWith("https://")) {
3222
+ checkFileAccess(resolve(project.config.root, path));
3223
+ }
3224
+ }
3207
3225
  }
3208
3226
  return vitest._testRun.recordArtifact(id, artifact);
3209
3227
  },
@@ -3516,4 +3534,4 @@ function defineBrowserProvider(options) {
3516
3534
  };
3517
3535
  }
3518
3536
 
3519
- export { createBrowserServer, defineBrowserCommand, defineBrowserProvider, parseKeyDef, resolveScreenshotPath };
3537
+ export { assertBrowserApiWrite, assertBrowserFileAccess, createBrowserServer, defineBrowserCommand, defineBrowserProvider, parseKeyDef, resolveScreenshotPath };