Repository: DevExpress/testcafe (js)

Issue:
hammerhead.js __get$(document, "location").ancestorOrigins returns <unreadable>
### What is your Scenario?

Fuse Microform iframes should be shown in given div containers.
See this doc: https://developer.cybersource.com/docs/cybs/en-us/digital-accept-flex/developer/all/rest/digital-accept-flex/microform-integ/microform-integ-getting-started/setting-up-client-side.html




### What is the Current behavior?

When I test it manually all works fine, when I run the test script I get the issue that `__get$(document, "location").ancestorOrigins` returns value unreadable as per screenshot below
![1](https://user-images.githubusercontent.com/11912960/167857174-61eab5a6-8872-47cb-b1d3-3ed57d55988a.png)

### What is the Expected behavior?

`__get$(document, "location").ancestorOrigins` returns 'http://localhost:12345'

### What is your public website URL? (or attach your complete example)

https://flex-microform-hammerhead-issue.azurewebsites.net/

### What is your TestCafe test code?

```
import { Selector } from 'testcafe';

fixture('Azure')
    .page('about:blank');

test('Azure', async t => {    
    await t
        .maximizeWindow()        
        .navigateTo('https://flex-microform-hammerhead-issue.azurewebsites.net/');

    var mainWindow = await t.getCurrentWindow() 

    try {
        await t
            .typeText('#cardholderName', 'Tester')
            .wait(1000);
        
        await t.debug();

        await t
            .switchToIframe('#number-container > iframe')
            .typeText('[name="number"]', '4111111111111111')
            .switchToWindow(mainWindow);
        
        await t
            .switchToIframe(`#securityCode-container > iframe`)
            .typeText('[name="securityCode"]', '123')
            .switchToWindow(mainWindow);

        await t
            .click('#pay-button');    

        await t
            .wait(1000)
            .click('#pay-button');    
    } catch (ex) {
        console.error(ex);
    }

    await t.debug();
});
```

### Your complete configuration file

.testcaferc.json
`{
    "skipJsErrors": true,
    "hostname": "localhost",
    "port1": 12345,
    "port2": 12346
}`

### Your complete test report

_No response_

### Screenshots

![1](https://user-images.githubusercontent.com/11912960/167855817-851b458c-5fa7-43ed-b9b5-24dd9e816b66.png)
![3](https://user-images.githubusercontent.com/11912960/167855851-30739a20-5b92-4fac-8008-0aa7f6ba2f89.png)
![2](https://user-images.githubusercontent.com/11912960/167855838-cace988a-ea20-4672-84ed-707e0ae48a4c.png)


### Steps to Reproduce

1. Run automated script in chrome
2. When script stops open dev tools
3. Switch to Console
4. Unclock the page and press `Mount Security Code` button
5. Expected error should be 
```
correlationId: undefined
details: undefined
informationLink: "https://www.cybersource.com/products/payment_security/secure_acceptance"
message: "You have not supplied a valid capture context."
name: "MicroformError"
reason: "CAPTURE_CONTEXT_INVALID"
```
6. Open iframe.min.js and format it it so that it's not minified, add breakpoint in line 2772 as per screenshot below
![4](https://user-images.githubusercontent.com/11912960/167860987-77eb0fc6-b7e0-4813-a19b-2d6fbfb571e3.png)
7. Press `Mount Security Code` button again and in debug inspect `document` and `__get$(document, "location")` variables  

### TestCafe version

1.18.6

### Node.js version

v14.17.6

### Command-line arguments

testcafe chrome azure.js

### Browser name(s) and version(s)

Chrome 101.0.4951.54 / Windows 10

### Platform(s) and version(s)

Windows

### Other

_No response_

Pull request:
feat: added the possibility to disable a cross-domain emulation
<!--
Thank you for your contribution.

Before making a PR, please read our contributing guidelines at
https://github.com/DevExpress/testcafe/blob/master/CONTRIBUTING.md#code-contribution

We recommend creating a *draft* PR, so that you can mark it as 'ready for review' when you are done.
-->
[closes #7022]

## Purpose
Add `--disable-cross-domain` option

## Approach
1. Add handling CLI option `--disable-cross-domain`
2. Add switching cross-domain

## References
https://github.com/DevExpress/testcafe/issues/7022
https://github.com/DevExpress/testcafe-hammerhead/pull/2806

## Pre-Merge TODO
- [ ] Write tests for your proposed changes
- [ ] Make sure that existing tests do not fail


Patch:
diff --git a/package.json b/package.json
index 57d1ccd99..35ad16b73 100644
--- a/package.json
+++ b/package.json
@@ -139,7 +139,7 @@
     "source-map-support": "^0.5.16",
     "strip-bom": "^2.0.0",
     "testcafe-browser-tools": "2.0.23",
-    "testcafe-hammerhead": "28.0.0",
+    "testcafe-hammerhead": "28.1.0",
     "testcafe-legacy-api": "5.1.6",
     "testcafe-reporter-dashboard": "^0.2.7-rc.1",
     "testcafe-reporter-json": "^2.1.0",
diff --git a/src/browser/provider/built-in/dedicated/chrome/cdp-client/index.ts b/src/browser/provider/built-in/dedicated/chrome/cdp-client/index.ts
index ed614e5b1..6a2e1c396 100644
--- a/src/browser/provider/built-in/dedicated/chrome/cdp-client/index.ts
+++ b/src/browser/provider/built-in/dedicated/chrome/cdp-client/index.ts
@@ -201,15 +201,15 @@ export class BrowserClient {
     public async resizeWindow (newDimensions: Size): Promise<void> {
         const { browserId, config, viewportSize, providerMethods, emulatedDevicePixelRatio } = this._runtimeInfo;
 
-        const currentWidth = viewportSize.width;
+        const currentWidth  = viewportSize.width;
         const currentHeight = viewportSize.height;
-        const newWidth = newDimensions.width || currentWidth;
-        const newHeight = newDimensions.height || currentHeight;
+        const newWidth      = newDimensions.width || currentWidth;
+        const newHeight     = newDimensions.height || currentHeight;
 
         if (!config.headless)
             await providerMethods.resizeLocalBrowserWindow(browserId, newWidth, newHeight, currentWidth, currentHeight);
 
-        viewportSize.width = newWidth;
+        viewportSize.width  = newWidth;
         viewportSize.height = newHeight;
 
         const client = await this.getActiveClient();
@@ -281,10 +281,7 @@ export class BrowserClient {
     }
 
     public async getScreenshotData (fullPage?: boolean): Promise<Buffer> {
-        let viewportWidth  = 0;
-        let viewportHeight = 0;
-
-        const { config, emulatedDevicePixelRatio } = this._runtimeInfo;
+        let clip: Protocol.Page.Viewport | undefined = void 0;
 
         const client = await this.getActiveClient();
 
@@ -296,35 +293,18 @@ export class BrowserClient {
         }
 
         if (fullPage) {
-            const { contentSize, visualViewport } = await client.Page.getLayoutMetrics();
-
-            await this._setDeviceMetricsOverride(
-                client,
-                Math.ceil(contentSize.width),
-                Math.ceil(contentSize.height),
-                emulatedDevicePixelRatio,
-                config.mobile);
+            const metrics           = await client.Page.getLayoutMetrics();
+            const { width, height } = metrics.cssContentSize || metrics.contentSize;
 
-            viewportWidth = visualViewport.clientWidth;
-            viewportHeight = visualViewport.clientHeight;
+            clip = { x: 0, y: 0, width, height, scale: 1 };
         }
 
-        const screenshotData = await client.Page.captureScreenshot({});
-
-        if (fullPage) {
-            if (config.emulation) {
-                await this._setDeviceMetricsOverride(
-                    client,
-                    config.width || viewportWidth,
-                    config.height || viewportHeight,
-                    emulatedDevicePixelRatio,
-                    config.mobile);
-            }
-            else
-                await client.Emulation.clearDeviceMetricsOverride();
-        }
+        const result = await client.Page.captureScreenshot({
+            clip,
+            captureBeyondViewport: fullPage,
+        });
 
-        return Buffer.from(screenshotData.data, 'base64');
+        return Buffer.from(result.data, 'base64');
     }
 
     public async closeTab (): Promise<void> {
@@ -342,7 +322,7 @@ export class BrowserClient {
 
         const windowDimensions = windowDimensionsQueryResult.result.value;
 
-        this._runtimeInfo.viewportSize.width = windowDimensions.outerWidth;
+        this._runtimeInfo.viewportSize.width  = windowDimensions.outerWidth;
         this._runtimeInfo.viewportSize.height = windowDimensions.outerHeight;
     }
 
diff --git a/src/cli/argument-parser/index.ts b/src/cli/argument-parser/index.ts
index 97a003722..fbbe3b1d2 100644
--- a/src/cli/argument-parser/index.ts
+++ b/src/cli/argument-parser/index.ts
@@ -211,6 +211,7 @@ export default class CLIArgumentParser {
             // NOTE: temporary hide experimental options from --help command
             .addOption(new Option('--proxyless', 'experimental').hideHelp())
             .addOption(new Option('--experimental-debug', 'enable experimental debug mode').hideHelp())
+            .addOption(new Option('--disable-cross-domain', 'experimental').hideHelp())
             .action((opts: CommandLineOptions) => {
                 this.opts = opts;
             });
diff --git a/src/cli/cli.js b/src/cli/cli.js
index dccf6a287..0ebb06110 100644
--- a/src/cli/cli.js
+++ b/src/cli/cli.js
@@ -86,6 +86,7 @@ async function runTests (argParser) {
         disableHttp2,
         v8Flags,
         proxyless,
+        disableCrossDomain,
     } = opts;
 
     const testCafe = await createTestCafe({
@@ -103,6 +104,7 @@ async function runTests (argParser) {
         disableHttp2,
         v8Flags,
         proxyless,
+        disableCrossDomain,
     });
 
     const correctedBrowsersAndSources = await correctBrowsersAndSources(argParser, testCafe.configuration);
diff --git a/src/configuration/default-values.ts b/src/configuration/default-values.ts
index 1039e5868..b64b11baa 100644
--- a/src/configuration/default-values.ts
+++ b/src/configuration/default-values.ts
@@ -21,6 +21,7 @@ export const DEFAULT_DISABLE_HTTP2         = false;
 export const DEFAULT_PROXYLESS             = false;
 export const DEFAULT_SCREENSHOT_THUMBNAILS = true;
 export const DEFAULT_FILTER_FN             = null;
+export const DEFAULT_DISABLE_CROSS_DOMAIN  = false;
 
 export const DEFAULT_TYPESCRIPT_COMPILER_OPTIONS: Dictionary<boolean | number> = {
     experimentalDecorators:  true,
diff --git a/src/configuration/option-names.ts b/src/configuration/option-names.ts
index b69e7fbe6..4233e5e42 100644
--- a/src/configuration/option-names.ts
+++ b/src/configuration/option-names.ts
@@ -55,6 +55,7 @@ enum OptionNames {
     hooks = 'hooks',
     dashboard = 'dashboard',
     baseUrl = 'baseUrl',
+    disableCrossDomain = 'disableCrossDomain',
 }
 
 export default OptionNames;
diff --git a/src/services/compiler/host.ts b/src/services/compiler/host.ts
index fe5868802..7cb4c280d 100644
--- a/src/services/compiler/host.ts
+++ b/src/services/compiler/host.ts
@@ -108,7 +108,7 @@ interface CompilerHostInitOptions {
 }
 
 export default class CompilerHost extends AsyncEventEmitter implements CompilerProtocol {
-    private runtime: Promise<RuntimeResources|undefined>;
+    private runtime: Promise<RuntimeResources | undefined>;
     private cdp: cdp.ProtocolApi & EventEmitter | undefined;
     private readonly developmentMode: boolean;
     private readonly v8Flags: string[];
@@ -246,14 +246,14 @@ export default class CompilerHost extends AsyncEventEmitter implements CompilerP
             args = this.v8Flags.filter(flag => !INSPECT_RE.test(flag));
 
         // TODO: debugging: refactor to a separate debug info parsing unit
-        const inspectBrkFlag = `--inspect-brk=127.0.0.1:${port}`;
+        const inspectBrkFlag = `--inspect-brk=127.0.0.1:${ port }`;
 
         args.push(inspectBrkFlag, SERVICE_PATH);
 
         return args;
     }
 
-    private async _init (runtime: Promise<RuntimeResources|undefined>): Promise<RuntimeResources|undefined> {
+    private async _init (runtime: Promise<RuntimeResources | undefined>): Promise<RuntimeResources | undefined> {
         const resolvedRuntime = await runtime;
 
         if (resolvedRuntime)


Test patch:
diff --git a/src/configuration/testcafe-configuration.ts b/src/configuration/testcafe-configuration.ts
index 7183f5858..4ae7639ca 100644
--- a/src/configuration/testcafe-configuration.ts
+++ b/src/configuration/testcafe-configuration.ts
@@ -13,6 +13,7 @@ import {
     DEFAULT_APP_INIT_DELAY,
     DEFAULT_CONCURRENCY_VALUE,
     DEFAULT_DEVELOPMENT_MODE,
+    DEFAULT_DISABLE_CROSS_DOMAIN,
     DEFAULT_DISABLE_HTTP2,
     DEFAULT_FILTER_FN,
     DEFAULT_PROXYLESS,
@@ -65,6 +66,7 @@ const OPTION_INIT_FLAG_NAMES = [
     OPTION_NAMES.cache,
     OPTION_NAMES.disableHttp2,
     OPTION_NAMES.proxyless,
+    OPTION_NAMES.disableCrossDomain,
 ];
 
 interface TestCafeAdditionalStartOptions {
@@ -74,6 +76,7 @@ interface TestCafeAdditionalStartOptions {
     cache: boolean;
     disableHttp2: boolean;
     proxyless: boolean;
+    disableCrossDomain: boolean;
 }
 
 interface TestCafeStartOptions {
@@ -155,12 +158,13 @@ export default class TestCafeConfiguration extends Configuration {
             port2:    this.getOption(OPTION_NAMES.port2) as number,
 
             options: {
-                ssl:             this.getOption(OPTION_NAMES.ssl) as string,
-                developmentMode: this.getOption(OPTION_NAMES.developmentMode) as boolean,
-                retryTestPages:  this.getOption(OPTION_NAMES.retryTestPages) as boolean,
-                cache:           this.getOption(OPTION_NAMES.cache) as boolean,
-                disableHttp2:    this.getOption(OPTION_NAMES.disableHttp2) as boolean,
-                proxyless:       this.getOption(OPTION_NAMES.proxyless) as boolean,
+                ssl:                this.getOption(OPTION_NAMES.ssl) as string,
+                developmentMode:    this.getOption(OPTION_NAMES.developmentMode) as boolean,
+                retryTestPages:     this.getOption(OPTION_NAMES.retryTestPages) as boolean,
+                cache:              this.getOption(OPTION_NAMES.cache) as boolean,
+                disableHttp2:       this.getOption(OPTION_NAMES.disableHttp2) as boolean,
+                proxyless:          this.getOption(OPTION_NAMES.proxyless) as boolean,
+                disableCrossDomain: this.getOption(OPTION_NAMES.disableCrossDomain) as boolean,
             },
         };
 
@@ -266,6 +270,7 @@ export default class TestCafeConfiguration extends Configuration {
         this._ensureOptionWithValue(OPTION_NAMES.retryTestPages, DEFAULT_RETRY_TEST_PAGES, OptionSource.Configuration);
         this._ensureOptionWithValue(OPTION_NAMES.disableHttp2, DEFAULT_DISABLE_HTTP2, OptionSource.Configuration);
         this._ensureOptionWithValue(OPTION_NAMES.proxyless, DEFAULT_PROXYLESS, OptionSource.Configuration);
+        this._ensureOptionWithValue(OPTION_NAMES.disableCrossDomain, DEFAULT_DISABLE_CROSS_DOMAIN, OptionSource.Configuration);
 
         this._ensureScreenshotOptions();
         this._ensureSkipJsOptions();
diff --git a/test/functional/fixtures/regression/gh-5961/pages/index.html b/test/functional/fixtures/regression/gh-5961/pages/index.html
new file mode 100644
index 000000000..5a28b2a6e
--- /dev/null
+++ b/test/functional/fixtures/regression/gh-5961/pages/index.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <title>Testcafe screenshot sizing issue</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+        }
+
+        body {
+            background-color: black;
+        }
+
+        div.wrapper {
+            position: relative;
+            height: 5000px;
+        }
+
+        div.d {
+            width: 50px;
+            height: 50px;
+            position: absolute;
+        }
+
+        .left {
+            left: 0;
+        }
+
+        .right {
+            right: 0;
+        }
+
+        .top {
+            top: 0;
+        }
+
+        .bottom {
+            bottom: 0;
+        }
+
+        .red {
+            background-color: rgb(255, 0, 0);
+        }
+
+        .green {
+            background-color: rgb(0, 255, 0);
+        }
+
+        @media screen and (max-width: 1023px) {
+            .red {
+                background-color: rgb(0, 255, 0);
+            }
+
+            .green {
+                background-color: rgb(255, 0, 0);
+            }
+        }
+    </style>
+</head>
+
+<body>
+    <div class="wrapper">
+        <div class="d left top red"></div>
+        <div class="d right top green"></div>
+        <div class="d left bottom green"></div>
+        <div class="d right bottom red"></div>
+    </div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/test/functional/fixtures/regression/gh-5961/test.js b/test/functional/fixtures/regression/gh-5961/test.js
new file mode 100644
index 000000000..7e9878665
--- /dev/null
+++ b/test/functional/fixtures/regression/gh-5961/test.js
@@ -0,0 +1,20 @@
+const assertionHelper             = require('../../../assertion-helper.js');
+const { expect }                  = require('chai');
+const config                      = require('../../../config.js');
+const { skipInExperimentalDebug } = require('../../../utils/skip-in.js');
+
+if (config.useLocalBrowsers && config.useHeadlessBrowsers) {
+    describe('[Regression](GH-5961)', () => {
+        afterEach(assertionHelper.removeScreenshotDir);
+
+        skipInExperimentalDebug('Screenshot', () => {
+            return runTests('./testcafe-fixtures/index.js', 'Take a resized full page screenshot', { setScreenshotPath: true })
+                .then(function () {
+                    return assertionHelper.checkScreenshotFileFullPage(false, 'custom');
+                })
+                .then(function (result) {
+                    expect(result).eql(true);
+                });
+        });
+    });
+}
diff --git a/test/functional/fixtures/regression/gh-5961/testcafe-fixtures/index.js b/test/functional/fixtures/regression/gh-5961/testcafe-fixtures/index.js
new file mode 100644
index 000000000..8d3e139e3
--- /dev/null
+++ b/test/functional/fixtures/regression/gh-5961/testcafe-fixtures/index.js
@@ -0,0 +1,15 @@
+import { ClientFunction } from 'testcafe';
+import { parseUserAgent } from '../../../../../../lib/utils/parse-user-agent';
+
+fixture`Getting Started`
+    .page`http://localhost:3000/fixtures/regression/gh-5961/pages/index.html`;
+
+test('Take a resized full page screenshot', async t => {
+    const ua = await ClientFunction(() => navigator.userAgent.toString())();
+
+    await t.resizeWindow(1024, 768);
+    await t.takeScreenshot({
+        path:     'custom/' + parseUserAgent(ua).name + '.png',
+        fullPage: true,
+    });
+});
diff --git a/test/server/cli-argument-parser-test.js b/test/server/cli-argument-parser-test.js
index ea4d022de..ece1f42a9 100644
--- a/test/server/cli-argument-parser-test.js
+++ b/test/server/cli-argument-parser-test.js
@@ -753,7 +753,7 @@ describe('CLI argument parser', function () {
     });
 
     it('Should parse command line arguments', function () {
-        return parse('-r list -S -q -e message=/testMessage/i,stack=testStack,pageUrl=testPageUrl --hostname myhost --base-url localhost:3000 --proxy localhost:1234 --proxy-bypass localhost:5678 --qr-code --app run-app --speed 0.5 --debug-on-fail --disable-page-reloads --retry-test-pages --dev --sf --disable-page-caching --disable-http2 --proxyless ie test/server/data/file-list/file-1.js')
+        return parse('-r list -S -q -e message=/testMessage/i,stack=testStack,pageUrl=testPageUrl --hostname myhost --base-url localhost:3000 --proxy localhost:1234 --proxy-bypass localhost:5678 --qr-code --app run-app --speed 0.5 --debug-on-fail --disable-page-reloads --retry-test-pages --dev --sf --disable-page-caching --disable-http2 --proxyless --disable-cross-domain ie test/server/data/file-list/file-1.js')
             .then(parser => {
                 expect(parser.opts.browsers).eql(['ie']);
                 expect(parser.opts.src).eql(['test/server/data/file-list/file-1.js']);
@@ -779,6 +779,7 @@ describe('CLI argument parser', function () {
                 expect(parser.opts.disablePageReloads).to.be.ok;
                 expect(parser.opts.retryTestPages).to.be.ok;
                 expect(parser.opts.disableHttp2).to.be.ok;
+                expect(parser.opts.disableCrossDomain).to.be.ok;
                 expect(parser.opts.proxyless).to.be.ok;
                 expect(parser.opts.baseUrl).eql('localhost:3000');
             });
@@ -857,6 +858,7 @@ describe('CLI argument parser', function () {
             { long: '--disable-http2' },
             { long: '--proxyless' },
             { long: '--base-url' },
+            { long: '--disable-cross-domain' },
         ];
 
         const parser  = new CliArgumentParser('');
@@ -873,7 +875,7 @@ describe('CLI argument parser', function () {
         }
 
         const expectedRunOptionsCount   = 22;
-        const expectedOtherOptionsCount = 36;
+        const expectedOtherOptionsCount = 37;
         const otherOptionsCount         = options.length - expectedRunOptionsCount;
 
         expect(runOptionNames.length).eql(expectedRunOptionsCount, ADD_TO_RUN_OPTIONS_WARNING);