1/** 2 * Copyright 2018 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import expect from 'expect'; 18import { 19 getTestState, 20 setupTestBrowserHooks, 21 setupTestPageAndContextHooks, 22 itFailsFirefox, 23} from './mocha-utils'; // eslint-disable-line import/extensions 24 25describe('JSHandle', function () { 26 setupTestBrowserHooks(); 27 setupTestPageAndContextHooks(); 28 29 describe('Page.evaluateHandle', function () { 30 it('should work', async () => { 31 const { page } = getTestState(); 32 33 const windowHandle = await page.evaluateHandle(() => window); 34 expect(windowHandle).toBeTruthy(); 35 }); 36 it('should accept object handle as an argument', async () => { 37 const { page } = getTestState(); 38 39 const navigatorHandle = await page.evaluateHandle(() => navigator); 40 const text = await page.evaluate( 41 (e: Navigator) => e.userAgent, 42 navigatorHandle 43 ); 44 expect(text).toContain('Mozilla'); 45 }); 46 it('should accept object handle to primitive types', async () => { 47 const { page } = getTestState(); 48 49 const aHandle = await page.evaluateHandle(() => 5); 50 const isFive = await page.evaluate((e) => Object.is(e, 5), aHandle); 51 expect(isFive).toBeTruthy(); 52 }); 53 it('should warn on nested object handles', async () => { 54 const { page } = getTestState(); 55 56 const aHandle = await page.evaluateHandle(() => document.body); 57 let error = null; 58 await page 59 // @ts-expect-error we are deliberately passing a bad type here (nested object) 60 .evaluateHandle((opts) => opts.elem.querySelector('p'), { 61 elem: aHandle, 62 }) 63 .catch((error_) => (error = error_)); 64 expect(error.message).toContain('Are you passing a nested JSHandle?'); 65 }); 66 it('should accept object handle to unserializable value', async () => { 67 const { page } = getTestState(); 68 69 const aHandle = await page.evaluateHandle(() => Infinity); 70 expect(await page.evaluate((e) => Object.is(e, Infinity), aHandle)).toBe( 71 true 72 ); 73 }); 74 it('should use the same JS wrappers', async () => { 75 const { page } = getTestState(); 76 77 const aHandle = await page.evaluateHandle(() => { 78 globalThis.FOO = 123; 79 return window; 80 }); 81 expect(await page.evaluate((e: { FOO: number }) => e.FOO, aHandle)).toBe( 82 123 83 ); 84 }); 85 it('should work with primitives', async () => { 86 const { page } = getTestState(); 87 88 const aHandle = await page.evaluateHandle(() => { 89 globalThis.FOO = 123; 90 return window; 91 }); 92 expect(await page.evaluate((e: { FOO: number }) => e.FOO, aHandle)).toBe( 93 123 94 ); 95 }); 96 }); 97 98 describe('JSHandle.getProperty', function () { 99 it('should work', async () => { 100 const { page } = getTestState(); 101 102 const aHandle = await page.evaluateHandle(() => ({ 103 one: 1, 104 two: 2, 105 three: 3, 106 })); 107 const twoHandle = await aHandle.getProperty('two'); 108 expect(await twoHandle.jsonValue()).toEqual(2); 109 }); 110 }); 111 112 describe('JSHandle.jsonValue', function () { 113 it('should work', async () => { 114 const { page } = getTestState(); 115 116 const aHandle = await page.evaluateHandle(() => ({ foo: 'bar' })); 117 const json = await aHandle.jsonValue<Record<string, string>>(); 118 expect(json).toEqual({ foo: 'bar' }); 119 }); 120 121 it('works with jsonValues that are not objects', async () => { 122 const { page } = getTestState(); 123 124 const aHandle = await page.evaluateHandle(() => ['a', 'b']); 125 const json = await aHandle.jsonValue<string[]>(); 126 expect(json).toEqual(['a', 'b']); 127 }); 128 129 it('works with jsonValues that are primitives', async () => { 130 const { page } = getTestState(); 131 132 const aHandle = await page.evaluateHandle(() => 'foo'); 133 const json = await aHandle.jsonValue<string>(); 134 expect(json).toEqual('foo'); 135 }); 136 137 it('should not work with dates', async () => { 138 const { page } = getTestState(); 139 140 const dateHandle = await page.evaluateHandle( 141 () => new Date('2017-09-26T00:00:00.000Z') 142 ); 143 const json = await dateHandle.jsonValue(); 144 expect(json).toEqual({}); 145 }); 146 it('should throw for circular objects', async () => { 147 const { page, isChrome } = getTestState(); 148 149 const windowHandle = await page.evaluateHandle('window'); 150 let error = null; 151 await windowHandle.jsonValue().catch((error_) => (error = error_)); 152 if (isChrome) 153 expect(error.message).toContain('Object reference chain is too long'); 154 else expect(error.message).toContain('Object is not serializable'); 155 }); 156 }); 157 158 describe('JSHandle.getProperties', function () { 159 it('should work', async () => { 160 const { page } = getTestState(); 161 162 const aHandle = await page.evaluateHandle(() => ({ 163 foo: 'bar', 164 })); 165 const properties = await aHandle.getProperties(); 166 const foo = properties.get('foo'); 167 expect(foo).toBeTruthy(); 168 expect(await foo.jsonValue()).toBe('bar'); 169 }); 170 it('should return even non-own properties', async () => { 171 const { page } = getTestState(); 172 173 const aHandle = await page.evaluateHandle(() => { 174 class A { 175 a: string; 176 constructor() { 177 this.a = '1'; 178 } 179 } 180 class B extends A { 181 b: string; 182 constructor() { 183 super(); 184 this.b = '2'; 185 } 186 } 187 return new B(); 188 }); 189 const properties = await aHandle.getProperties(); 190 expect(await properties.get('a').jsonValue()).toBe('1'); 191 expect(await properties.get('b').jsonValue()).toBe('2'); 192 }); 193 }); 194 195 describe('JSHandle.asElement', function () { 196 it('should work', async () => { 197 const { page } = getTestState(); 198 199 const aHandle = await page.evaluateHandle(() => document.body); 200 const element = aHandle.asElement(); 201 expect(element).toBeTruthy(); 202 }); 203 it('should return null for non-elements', async () => { 204 const { page } = getTestState(); 205 206 const aHandle = await page.evaluateHandle(() => 2); 207 const element = aHandle.asElement(); 208 expect(element).toBeFalsy(); 209 }); 210 it('should return ElementHandle for TextNodes', async () => { 211 const { page } = getTestState(); 212 213 await page.setContent('<div>ee!</div>'); 214 const aHandle = await page.evaluateHandle( 215 () => document.querySelector('div').firstChild 216 ); 217 const element = aHandle.asElement(); 218 expect(element).toBeTruthy(); 219 expect( 220 await page.evaluate( 221 (e: HTMLElement) => e.nodeType === Node.TEXT_NODE, 222 element 223 ) 224 ); 225 }); 226 }); 227 228 describe('JSHandle.toString', function () { 229 it('should work for primitives', async () => { 230 const { page } = getTestState(); 231 232 const numberHandle = await page.evaluateHandle(() => 2); 233 expect(numberHandle.toString()).toBe('JSHandle:2'); 234 const stringHandle = await page.evaluateHandle(() => 'a'); 235 expect(stringHandle.toString()).toBe('JSHandle:a'); 236 }); 237 it('should work for complicated objects', async () => { 238 const { page } = getTestState(); 239 240 const aHandle = await page.evaluateHandle(() => window); 241 expect(aHandle.toString()).toBe('JSHandle@object'); 242 }); 243 it('should work with different subtypes', async () => { 244 const { page } = getTestState(); 245 246 expect((await page.evaluateHandle('(function(){})')).toString()).toBe( 247 'JSHandle@function' 248 ); 249 expect((await page.evaluateHandle('12')).toString()).toBe('JSHandle:12'); 250 expect((await page.evaluateHandle('true')).toString()).toBe( 251 'JSHandle:true' 252 ); 253 expect((await page.evaluateHandle('undefined')).toString()).toBe( 254 'JSHandle:undefined' 255 ); 256 expect((await page.evaluateHandle('"foo"')).toString()).toBe( 257 'JSHandle:foo' 258 ); 259 expect((await page.evaluateHandle('Symbol()')).toString()).toBe( 260 'JSHandle@symbol' 261 ); 262 expect((await page.evaluateHandle('new Map()')).toString()).toBe( 263 'JSHandle@map' 264 ); 265 expect((await page.evaluateHandle('new Set()')).toString()).toBe( 266 'JSHandle@set' 267 ); 268 expect((await page.evaluateHandle('[]')).toString()).toBe( 269 'JSHandle@array' 270 ); 271 expect((await page.evaluateHandle('null')).toString()).toBe( 272 'JSHandle:null' 273 ); 274 expect((await page.evaluateHandle('/foo/')).toString()).toBe( 275 'JSHandle@regexp' 276 ); 277 expect((await page.evaluateHandle('document.body')).toString()).toBe( 278 'JSHandle@node' 279 ); 280 expect((await page.evaluateHandle('new Date()')).toString()).toBe( 281 'JSHandle@date' 282 ); 283 expect((await page.evaluateHandle('new WeakMap()')).toString()).toBe( 284 'JSHandle@weakmap' 285 ); 286 expect((await page.evaluateHandle('new WeakSet()')).toString()).toBe( 287 'JSHandle@weakset' 288 ); 289 expect((await page.evaluateHandle('new Error()')).toString()).toBe( 290 'JSHandle@error' 291 ); 292 expect((await page.evaluateHandle('new Int32Array()')).toString()).toBe( 293 'JSHandle@typedarray' 294 ); 295 expect((await page.evaluateHandle('new Proxy({}, {})')).toString()).toBe( 296 'JSHandle@proxy' 297 ); 298 }); 299 }); 300}); 301