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