2020-11-13 14:24:53 -08:00
/ * *
* Copyright 2018 Google Inc . All rights reserved .
* Modifications copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2024-09-19 03:12:21 -07:00
import { attachFrame } from 'tests/config/utils' ;
2022-03-25 15:05:50 -08:00
import { browserTest as it , expect } from '../config/browserTest' ;
2021-02-11 06:36:15 -08:00
import fs from 'fs' ;
2020-11-13 14:24:53 -08:00
2021-04-02 21:07:45 -07:00
it ( 'should capture local storage' , async ( { contextFactory } ) = > {
const context = await contextFactory ( ) ;
2020-11-13 14:24:53 -08:00
const page1 = await context . newPage ( ) ;
await page1 . route ( '**/*' , route = > {
route . fulfill ( { body : '<html></html>' } ) . catch ( ( ) = > { } ) ;
} ) ;
await page1 . goto ( 'https://www.example.com' ) ;
await page1 . evaluate ( ( ) = > {
localStorage [ 'name1' ] = 'value1' ;
} ) ;
await page1 . goto ( 'https://www.domain.com' ) ;
await page1 . evaluate ( ( ) = > {
localStorage [ 'name2' ] = 'value2' ;
} ) ;
const { origins } = await context . storageState ( ) ;
expect ( origins ) . toEqual ( [ {
origin : 'https://www.domain.com' ,
localStorage : [ {
name : 'name2' ,
value : 'value2'
} ] ,
2024-03-12 19:20:35 -07:00
} , {
origin : 'https://www.example.com' ,
localStorage : [ {
name : 'name1' ,
value : 'value1'
} ] ,
2020-11-13 14:24:53 -08:00
} ] ) ;
} ) ;
2022-03-18 17:17:37 -08:00
it ( 'should set local storage' , async ( { contextFactory } ) = > {
const context = await contextFactory ( {
2020-11-13 14:24:53 -08:00
storageState : {
2021-10-06 09:02:41 -07:00
cookies : [ ] ,
2020-11-13 14:24:53 -08:00
origins : [
{
origin : 'https://www.example.com' ,
localStorage : [ {
name : 'name1' ,
value : 'value1'
} ]
} ,
]
}
} ) ;
const page = await context . newPage ( ) ;
await page . route ( '**/*' , route = > {
route . fulfill ( { body : '<html></html>' } ) . catch ( ( ) = > { } ) ;
} ) ;
await page . goto ( 'https://www.example.com' ) ;
const localStorage = await page . evaluate ( 'window.localStorage' ) ;
expect ( localStorage ) . toEqual ( { name1 : 'value1' } ) ;
await context . close ( ) ;
} ) ;
2020-12-14 16:03:52 -08:00
2021-04-02 21:07:45 -07:00
it ( 'should round-trip through the file' , async ( { contextFactory } , testInfo ) = > {
const context = await contextFactory ( ) ;
2020-12-14 16:03:52 -08:00
const page1 = await context . newPage ( ) ;
await page1 . route ( '**/*' , route = > {
route . fulfill ( { body : '<html></html>' } ) . catch ( ( ) = > { } ) ;
} ) ;
await page1 . goto ( 'https://www.example.com' ) ;
2025-02-05 15:01:53 +01:00
await page1 . evaluate ( async ( ) = > {
2020-12-14 16:03:52 -08:00
localStorage [ 'name1' ] = 'value1' ;
document . cookie = 'username=John Doe' ;
2025-02-05 15:01:53 +01:00
await new Promise ( ( resolve , reject ) = > {
const openRequest = indexedDB . open ( 'db' , 42 ) ;
openRequest . onupgradeneeded = ( ) = > {
2025-02-06 12:39:19 +01:00
openRequest . result . createObjectStore ( 'store' , { keyPath : 'name' } ) ;
openRequest . result . createObjectStore ( 'store2' ) ;
2025-02-05 15:01:53 +01:00
} ;
openRequest . onsuccess = ( ) = > {
2025-02-06 12:39:19 +01:00
const transaction = openRequest . result . transaction ( [ 'store' , 'store2' ] , 'readwrite' ) ;
transaction
2025-02-05 15:01:53 +01:00
. objectStore ( 'store' )
2025-03-21 10:24:28 +01:00
. put ( { name : 'foo' , date : new Date ( 0 ) , null : null } ) ;
2025-02-06 12:39:19 +01:00
transaction
. objectStore ( 'store2' )
2025-03-26 18:04:45 +01:00
. put ( new TextEncoder ( ) . encode ( 'bar' ) , 'foo' ) ;
2025-02-06 12:39:19 +01:00
transaction . addEventListener ( 'complete' , resolve ) ;
transaction . addEventListener ( 'error' , reject ) ;
2025-02-05 15:01:53 +01:00
} ;
} ) ;
2020-12-14 16:03:52 -08:00
return document . cookie ;
} ) ;
const path = testInfo . outputPath ( 'storage-state.json' ) ;
2025-02-27 14:27:54 +01:00
const state = await context . storageState ( { path , indexedDB : true } ) ;
2020-12-14 16:03:52 -08:00
const written = await fs . promises . readFile ( path , 'utf8' ) ;
2021-03-16 10:03:09 +08:00
expect ( JSON . stringify ( state , undefined , 2 ) ) . toBe ( written ) ;
2020-12-14 16:03:52 -08:00
2021-04-02 21:07:45 -07:00
const context2 = await contextFactory ( { storageState : path } ) ;
2020-12-14 16:03:52 -08:00
const page2 = await context2 . newPage ( ) ;
await page2 . route ( '**/*' , route = > {
route . fulfill ( { body : '<html></html>' } ) . catch ( ( ) = > { } ) ;
} ) ;
await page2 . goto ( 'https://www.example.com' ) ;
const localStorage = await page2 . evaluate ( 'window.localStorage' ) ;
expect ( localStorage ) . toEqual ( { name1 : 'value1' } ) ;
const cookie = await page2 . evaluate ( 'document.cookie' ) ;
expect ( cookie ) . toEqual ( 'username=John Doe' ) ;
2025-02-06 12:39:19 +01:00
const idbValues = await page2 . evaluate ( ( ) = > new Promise ( ( resolve , reject ) = > {
2025-02-05 15:01:53 +01:00
const openRequest = indexedDB . open ( 'db' , 42 ) ;
2025-03-26 18:04:45 +01:00
openRequest . addEventListener ( 'success' , async ( ) = > {
2025-02-05 15:01:53 +01:00
const db = openRequest . result ;
2025-02-06 12:39:19 +01:00
const transaction = db . transaction ( [ 'store' , 'store2' ] , 'readonly' ) ;
const request1 = transaction . objectStore ( 'store' ) . get ( 'foo' ) ;
const request2 = transaction . objectStore ( 'store2' ) . get ( 'foo' ) ;
2025-03-26 18:04:45 +01:00
const [ result1 , result2 ] = await Promise . all ( [ request1 , request2 ] . map ( request = > new Promise ( ( resolve , reject ) = > {
2025-02-06 12:39:19 +01:00
request . addEventListener ( 'success' , ( ) = > resolve ( request . result ) ) ;
request . addEventListener ( 'error' , ( ) = > reject ( request . error ) ) ;
2025-03-26 18:04:45 +01:00
} ) ) ) ;
resolve ( [ result1 , new TextDecoder ( ) . decode ( result2 as any ) ] ) ;
2025-02-05 15:01:53 +01:00
} ) ;
openRequest . addEventListener ( 'error' , ( ) = > reject ( openRequest . error ) ) ;
} ) ) ;
2025-02-06 12:39:19 +01:00
expect ( idbValues ) . toEqual ( [
2025-03-21 10:24:28 +01:00
{ name : 'foo' , date : new Date ( 0 ) , null : null } ,
2025-02-06 12:39:19 +01:00
'bar'
] ) ;
2020-12-14 16:03:52 -08:00
await context2 . close ( ) ;
} ) ;
2021-10-07 15:37:47 -07:00
it ( 'should capture cookies' , async ( { server , context , page , contextFactory } ) = > {
server . setRoute ( '/setcookie.html' , ( req , res ) = > {
res . setHeader ( 'Set-Cookie' , [ 'a=b' , 'empty=' ] ) ;
res . end ( ) ;
} ) ;
await page . goto ( server . PREFIX + '/setcookie.html' ) ;
expect ( await page . evaluate ( ( ) = > {
const cookies = document . cookie . split ( ';' ) ;
return cookies . map ( cookie = > cookie . trim ( ) ) . sort ( ) ;
} ) ) . toEqual ( [
'a=b' ,
'empty=' ,
] ) ;
const storageState = await context . storageState ( ) ;
expect ( new Set ( storageState . cookies ) ) . toEqual ( new Set ( [
expect . objectContaining ( {
name : 'a' ,
value : 'b'
} ) ,
expect . objectContaining ( {
name : 'empty' ,
value : ''
} )
] ) ) ;
const context2 = await contextFactory ( { storageState } ) ;
const page2 = await context2 . newPage ( ) ;
await page2 . goto ( server . EMPTY_PAGE ) ;
expect ( await page2 . evaluate ( ( ) = > {
const cookies = document . cookie . split ( ';' ) ;
return cookies . map ( cookie = > cookie . trim ( ) ) . sort ( ) ;
} ) ) . toEqual ( [
'a=b' ,
'empty=' ,
] ) ;
} ) ;
2022-03-17 17:27:33 -08:00
it ( 'should not emit events about internal page' , async ( { contextFactory } ) = > {
const context = await contextFactory ( ) ;
const page = await context . newPage ( ) ;
await page . route ( '**/*' , route = > {
2023-06-02 21:59:12 +02:00
void route . fulfill ( { body : '<html></html>' } ) ;
2022-03-17 17:27:33 -08:00
} ) ;
await page . goto ( 'https://www.example.com' ) ;
await page . evaluate ( ( ) = > localStorage [ 'name1' ] = 'value1' ) ;
await page . goto ( 'https://www.domain.com' ) ;
await page . evaluate ( ( ) = > localStorage [ 'name2' ] = 'value2' ) ;
const events = [ ] ;
context . on ( 'page' , e = > events . push ( e ) ) ;
context . on ( 'request' , e = > events . push ( e ) ) ;
context . on ( 'requestfailed' , e = > events . push ( e ) ) ;
context . on ( 'requestfinished' , e = > events . push ( e ) ) ;
context . on ( 'response' , e = > events . push ( e ) ) ;
await context . storageState ( ) ;
expect ( events ) . toHaveLength ( 0 ) ;
} ) ;
2022-03-18 17:17:37 -08:00
it ( 'should not restore localStorage twice' , async ( { contextFactory } ) = > {
const context = await contextFactory ( {
storageState : {
cookies : [ ] ,
origins : [
{
origin : 'https://www.example.com' ,
localStorage : [ {
name : 'name1' ,
value : 'value1'
} ]
} ,
]
}
} ) ;
const page = await context . newPage ( ) ;
await page . route ( '**/*' , route = > {
route . fulfill ( { body : '<html></html>' } ) . catch ( ( ) = > { } ) ;
} ) ;
await page . goto ( 'https://www.example.com' ) ;
const localStorage1 = await page . evaluate ( 'window.localStorage' ) ;
expect ( localStorage1 ) . toEqual ( { name1 : 'value1' } ) ;
await page . evaluate ( ( ) = > window . localStorage [ 'name1' ] = 'value2' ) ;
await page . goto ( 'https://www.example.com' ) ;
const localStorage2 = await page . evaluate ( 'window.localStorage' ) ;
expect ( localStorage2 ) . toEqual ( { name1 : 'value2' } ) ;
await context . close ( ) ;
} ) ;
2022-03-24 07:33:51 -07:00
it ( 'should handle missing file' , async ( { contextFactory } , testInfo ) = > {
const file = testInfo . outputPath ( 'does-not-exist.json' ) ;
const error = await contextFactory ( {
storageState : file ,
} ) . catch ( e = > e ) ;
expect ( error . message ) . toContain ( ` Error reading storage state from ${ file } : \ nENOENT ` ) ;
} ) ;
2024-10-25 10:33:43 -07:00
it ( 'should handle malformed file' , async ( { contextFactory , nodeVersion } , testInfo ) = > {
2022-03-24 07:33:51 -07:00
const file = testInfo . outputPath ( 'state.json' ) ;
fs . writeFileSync ( file , 'not-json' , 'utf-8' ) ;
const error = await contextFactory ( {
storageState : file ,
} ) . catch ( e = > e ) ;
2024-10-25 10:33:43 -07:00
if ( nodeVersion . major > 18 )
2023-05-30 18:16:34 +02:00
expect ( error . message ) . toContain ( ` Error reading storage state from ${ file } : \ nUnexpected token 'o', \ "not-json \ " is not valid JSON ` ) ;
else
expect ( error . message ) . toContain ( ` Error reading storage state from ${ file } : \ nUnexpected token o in JSON at position 1 ` ) ;
2022-03-24 07:33:51 -07:00
} ) ;
2024-01-29 19:22:33 +01:00
it ( 'should serialize storageState with lone surrogates' , async ( { page , context , server } ) = > {
it . info ( ) . annotations . push ( { type : 'issue' , description : 'https://github.com/microsoft/playwright-dotnet/issues/2819' } ) ;
await page . goto ( server . EMPTY_PAGE ) ;
await page . evaluate ( ( ) = > window . localStorage . setItem ( 'foo' , String . fromCharCode ( 55934 ) ) ) ;
const storageState = await context . storageState ( ) ;
expect ( storageState . origins [ 0 ] . localStorage [ 0 ] . value ) . toBe ( String . fromCharCode ( 55934 ) ) ;
} ) ;
2024-03-12 19:20:35 -07:00
2024-06-21 00:43:26 +02:00
it ( 'should work when service worker is intefering' , async ( { page , context , server , isAndroid , isElectron , electronMajorVersion } ) = > {
2024-03-12 19:20:35 -07:00
it . skip ( isAndroid ) ;
2024-06-21 00:43:26 +02:00
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2024-03-12 19:20:35 -07:00
server . setRoute ( '/' , ( req , res ) = > {
res . writeHead ( 200 , { 'content-type' : 'text/html' } ) ;
res . end ( `
< script >
console . log ( 'from page' ) ;
window . localStorage . foo = 'bar' ;
window . registrationPromise = navigator . serviceWorker . register ( 'sw.js' ) ;
window . activationPromise = new Promise ( resolve = > navigator . serviceWorker . oncontrollerchange = resolve ) ;
< / script >
` );
} ) ;
server . setRoute ( '/sw.js' , ( req , res ) = > {
res . writeHead ( 200 , { 'content-type' : 'application/javascript' } ) ;
res . end ( `
const kHtmlPage = \ `
< script >
console . log ( 'from sw page' ) ;
let counter = window . localStorage . counter || 0 ;
++ counter ;
window . localStorage . counter = counter ;
setTimeout ( ( ) = > {
window . location . href = counter + '.html' ;
} , 0 ) ;
< / script >
\ ` ;
console . log ( 'from sw 1' ) ;
self . addEventListener ( 'fetch' , event = > {
console . log ( 'fetching ' + event . request . url ) ;
const blob = new Blob ( [ kHtmlPage ] , { type : 'text/html' } ) ;
const response = new Response ( blob , { status : 200 , statusText : 'OK' } ) ;
event . respondWith ( response ) ;
} ) ;
self . addEventListener ( 'activate' , event = > {
console . log ( 'from sw 2' ) ;
event . waitUntil ( clients . claim ( ) ) ;
} ) ;
` );
} ) ;
await page . goto ( server . PREFIX ) ;
await page . evaluate ( ( ) = > window [ 'activationPromise' ] ) ;
const storageState = await context . storageState ( ) ;
expect ( storageState . origins [ 0 ] . localStorage [ 0 ] ) . toEqual ( { name : 'foo' , value : 'bar' } ) ;
} ) ;
2024-09-19 03:12:21 -07:00
2024-09-20 08:28:46 -07:00
it ( 'should set local storage in third-party context' , async ( { contextFactory , server } ) = > {
2024-09-19 03:12:21 -07:00
const context = await contextFactory ( {
storageState : {
cookies : [ ] ,
origins : [
{
origin : server.CROSS_PROCESS_PREFIX ,
localStorage : [ {
name : 'name1' ,
value : 'value1'
} ]
} ,
]
}
} ) ;
const page = await context . newPage ( ) ;
await page . goto ( server . EMPTY_PAGE ) ;
const frame = await attachFrame ( page , 'frame1' , server . CROSS_PROCESS_PREFIX + '/empty.html' ) ;
const localStorage = await frame . evaluate ( 'window.localStorage' ) ;
expect ( localStorage ) . toEqual ( { name1 : 'value1' } ) ;
await context . close ( ) ;
} ) ;
2024-09-20 08:28:46 -07:00
it ( 'should roundtrip local storage in third-party context' , async ( { page , contextFactory , server } ) = > {
2024-09-19 03:12:21 -07:00
await page . goto ( server . EMPTY_PAGE ) ;
const frame = await attachFrame ( page , 'frame1' , server . CROSS_PROCESS_PREFIX + '/empty.html' ) ;
await frame . evaluate ( ( ) = > window . localStorage . setItem ( 'name1' , 'value1' ) ) ;
const storageState = await page . context ( ) . storageState ( ) ;
const context2 = await contextFactory ( { storageState } ) ;
const page2 = await context2 . newPage ( ) ;
await page2 . goto ( server . EMPTY_PAGE ) ;
const frame2 = await attachFrame ( page2 , 'frame1' , server . CROSS_PROCESS_PREFIX + '/empty.html' ) ;
const localStorage = await frame2 . evaluate ( 'window.localStorage' ) ;
expect ( localStorage ) . toEqual ( { name1 : 'value1' } ) ;
await context2 . close ( ) ;
} ) ;
2025-02-05 15:01:53 +01:00
it ( 'should support IndexedDB' , async ( { page , server , contextFactory } ) = > {
await page . goto ( server . PREFIX + '/to-do-notifications/index.html' ) ;
await page . getByLabel ( 'Task title' ) . fill ( 'Pet the cat' ) ;
await page . getByLabel ( 'Hours' ) . fill ( '1' ) ;
await page . getByLabel ( 'Mins' ) . fill ( '1' ) ;
await page . getByText ( 'Add Task' ) . click ( ) ;
2025-02-27 14:27:54 +01:00
const storageState = await page . context ( ) . storageState ( { indexedDB : true } ) ;
2025-02-05 15:01:53 +01:00
expect ( storageState . origins ) . toEqual ( [
{
origin : server.PREFIX ,
localStorage : [ ] ,
indexedDB : [
{
name : 'toDoList' ,
version : 4 ,
stores : [
{
name : 'toDoList' ,
autoIncrement : false ,
keyPath : 'taskTitle' ,
records : [
{
value : {
day : '01' ,
hours : '1' ,
minutes : '1' ,
month : 'January' ,
notified : 'no' ,
taskTitle : 'Pet the cat' ,
year : '2025' ,
} ,
} ,
] ,
indexes : [
{
name : 'day' ,
keyPath : 'day' ,
multiEntry : false ,
unique : false ,
} ,
{
name : 'hours' ,
keyPath : 'hours' ,
multiEntry : false ,
unique : false ,
} ,
{
name : 'minutes' ,
keyPath : 'minutes' ,
multiEntry : false ,
unique : false ,
} ,
{
name : 'month' ,
keyPath : 'month' ,
multiEntry : false ,
unique : false ,
} ,
{
name : 'notified' ,
keyPath : 'notified' ,
multiEntry : false ,
unique : false ,
} ,
{
name : 'year' ,
keyPath : 'year' ,
multiEntry : false ,
unique : false ,
} ,
] ,
} ,
] ,
} ,
] ,
} ,
] ) ;
const context = await contextFactory ( { storageState } ) ;
2025-02-27 14:27:54 +01:00
expect ( await context . storageState ( { indexedDB : true } ) ) . toEqual ( storageState ) ;
2025-02-05 15:01:53 +01:00
const recreatedPage = await context . newPage ( ) ;
await recreatedPage . goto ( server . PREFIX + '/to-do-notifications/index.html' ) ;
await expect ( recreatedPage . locator ( '#task-list' ) ) . toMatchAriaSnapshot ( `
- list :
- listitem :
- text : /Pet the cat/
` );
2025-02-06 16:40:14 +01:00
2025-02-27 14:27:54 +01:00
expect ( await context . storageState ( ) ) . toEqual ( { cookies : [ ] , origins : [ ] } ) ;
2025-02-05 15:01:53 +01:00
} ) ;
2025-04-28 16:20:57 +02:00
it ( 'should support empty indexedDB' , { annotation : { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/35760' } } , async ( { page , server , contextFactory } ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
await page . evaluate ( ( ) = > new Promise < void > ( resolve = > {
const openRequest = indexedDB . open ( 'unused-db' ) ;
openRequest . onsuccess = ( ) = > resolve ( ) ;
openRequest . onerror = ( ) = > resolve ( ) ;
} ) ) ;
const storageState = await page . context ( ) . storageState ( { indexedDB : true } ) ;
expect ( storageState . origins ) . toEqual ( [ {
origin : server.PREFIX ,
localStorage : [ ] ,
indexedDB : [ {
name : 'unused-db' ,
version : 1 ,
stores : [ ] ,
} ]
} ] ) ;
const context = await contextFactory ( { storageState } ) ;
expect ( await context . storageState ( { indexedDB : true } ) ) . toEqual ( storageState ) ;
} ) ;