Repository: DevExpress/testcafe (js)

Issue:
'Uncaught object' error when previously clicked element is removed from the DOM and it is an <a> or <area> element
### What is your Scenario?

TestCafe (proxy mode) fails with 'Uncaught object' error on [leaveElement](https://github.com/DevExpress/testcafe/blob/bee56d2b1c0845ba95ba164842f98292449842f8/src/client/automation/playback/move/event-sequence/base.js#L38) when the [prevElement](https://github.com/DevExpress/testcafe/blob/bee56d2b1c0845ba95ba164842f98292449842f8/src/client/automation/playback/move/event-sequence/base.js#L38C47-L38C58) is removed from the DOM, and it (or its parent, which is also removed) is an `<a>` or `<area>` element. The issue happens because subsequent [getParent](https://github.com/DevExpress/testcafe-hammerhead/blob/4b81b8b460696c602fc6c241e4376da91a826a26/src/client/utils/dom.ts#L837) hammerhead's code returns "host", which is a string representing the host in case of `<a>` and `<area>` elements, not an element. This causes a failure when attempting to get its parent again using `nativeMethods.nodeParentNodeGetter.call(el)`.

From the user's perspective, it appears that TestCafe attempts to move the mouse very slowly to the next element and then fails with an `Uncaught object` error.

Issue is not reproducible in native automation mode.


### What is the Current behavior?

Test execution fails with error:
```
  1) Uncaught object

   "{"callsite":{"filename":"C:\\work\\git\\testcafe-callsite-issue\\test.js","lineNum":6,"callsiteFrameIdx":5,"stackFrames":[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}],"isV8Frames":true}}"
      was thrown. Throw Error instead.
```

### What is the Expected behavior?

Testcafe should execute test commands without error

### What is the public URL of the test page? (attach your complete example)

Test page to reproduce the issue
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Link Removal Example</title>
</head>
<body>
    <a href="https://www.google.com" id="link">Click me to remove</a>
    <button id="btn">Button</button>

    <script>
        document.getElementById('link').addEventListener('click', function(event) {
            event.preventDefault(); // Prevent the default link behavior
            this.remove(); // Remove the link element from the page
        });
    </script>
</body>
</html>
```

### What is your TestCafe test code?

```
fixture('TestCafe Callsite Issue')
    .page('./page.html');

test('Test link removal', async t => {
    await t
        .click('#link')
        .click('#btn');
});

```

### Your complete configuration file

_No response_

### Your complete test report

_No response_

### Screenshots

_No response_

### Steps to Reproduce

- Clone/download the repository: https://github.com/egorshantur/testcafe-callsite-issue
- Run the following command:
    `npm install`
- Run the test:
    `npm run test`

### TestCafe version

^3.4.0

### Node.js version

_No response_

### Command-line arguments

testcafe chrome test.js --disable-native-automation

### Browser name(s) and version(s)

_No response_

### Platform(s) and version(s)

_No response_

### Other

Tests running in proxy mode began failing after upgrading to version 3.4.0 or higher. The last version without this issue was 3.3.0.
I found that the issue is reproducible after the following change: [DevExpress/testcafe#7995](https://github.com/DevExpress/testcafe/pull/7995/files#diff-ec219363bae65deba245e68ce80584c923c4de802efec3934465e69cae217b40L34-L43)

If add removed code back, then test does not fail
```
        const prevElementInDocument = prevElement && domUtils.isElementInDocument(prevElement);

        const prevElementInRemovedIframe = prevElement && domUtils.isElementInIframe(prevElement) &&
                                         !domUtils.getIframeByElement(prevElement);

        if (!prevElementInDocument || prevElementInRemovedIframe)
            prevElement = null;
```



Based on the description "removed IE leftovers from src\client\automation," it should not affect my tests as I run them in Chrome. However, it did affect them, so I reviewed the code that was originally executed for all browsers but was removed in that commit, and identified the issue. 



Pull request description:
fix: add check prev element
<!--
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.
-->

## Purpose
_Describe the problem you want to address or the feature you want to implement._

## Approach
_Describe how your changes address the issue or implement the desired functionality in as much detail as possible._

## References
close #8264 

## Pre-Merge TODO
- [ ] Write tests for your proposed changes
- [ ] Make sure that existing tests do not fail


Patch:
diff --git a/package-lock.json b/package-lock.json
index e4748984e..0582ab574 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,17 +10,17 @@
       "license": "MIT",
       "dependencies": {
         "@babel/core": "^7.23.2",
-        "@babel/plugin-proposal-async-generator-functions": "^7.20.7",
-        "@babel/plugin-proposal-class-properties": "^7.18.6",
         "@babel/plugin-proposal-decorators": "^7.23.2",
-        "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
-        "@babel/plugin-proposal-private-methods": "^7.18.6",
         "@babel/plugin-syntax-dynamic-import": "^7.8.3",
         "@babel/plugin-syntax-import-meta": "^7.10.4",
+        "@babel/plugin-transform-async-generator-functions": "^7.25.4",
         "@babel/plugin-transform-async-to-generator": "^7.22.5",
+        "@babel/plugin-transform-class-properties": "^7.25.4",
         "@babel/plugin-transform-class-static-block": "^7.24.7",
         "@babel/plugin-transform-exponentiation-operator": "^7.22.5",
         "@babel/plugin-transform-for-of": "^7.22.15",
+        "@babel/plugin-transform-object-rest-spread": "^7.24.7",
+        "@babel/plugin-transform-private-methods": "^7.25.4",
         "@babel/plugin-transform-runtime": "7.23.3",
         "@babel/preset-env": "^7.23.2",
         "@babel/preset-flow": "^7.22.15",
@@ -98,7 +98,6 @@
         "testcafe-reporter-minimal": "^2.2.0",
         "testcafe-reporter-spec": "^2.2.0",
         "testcafe-reporter-xunit": "^2.2.1",
-        "testcafe-safe-storage": "^1.1.1",
         "testcafe-selector-generator": "^0.1.0",
         "time-limit-promise": "^1.0.2",
         "tmp": "0.0.28",
@@ -15800,12 +15799,6 @@
       "resolved": "https://registry.npmjs.org/testcafe-reporter-xunit/-/testcafe-reporter-xunit-2.2.3.tgz",
       "integrity": "sha512-aGyc+MZPsTNwd9SeKJSjFNwEZfILzFnObzOImaDbsf57disTQfEY+9japXWav/Ef5Cv04UEW24bTFl2Q4f8xwg=="
     },
-    "node_modules/testcafe-safe-storage": {
-      "version": "1.1.6",
-      "resolved": "https://registry.npmjs.org/testcafe-safe-storage/-/testcafe-safe-storage-1.1.6.tgz",
-      "integrity": "sha512-WFm1UcmO3uZs+uW8lYtBBJpnrvgTKkMQMKG9BvTEKbjeqhonEXVTxOkGEs3DM1ZB/ylPuwh7Jux7qUtjcM/D2Q==",
-      "deprecated": "The testcafe-safe-storage package has reached end-of-life and will not receive further updates."
-    },
     "node_modules/testcafe-selector-generator": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/testcafe-selector-generator/-/testcafe-selector-generator-0.1.0.tgz",
@@ -29038,11 +29031,6 @@
       "resolved": "https://registry.npmjs.org/testcafe-reporter-xunit/-/testcafe-reporter-xunit-2.2.3.tgz",
       "integrity": "sha512-aGyc+MZPsTNwd9SeKJSjFNwEZfILzFnObzOImaDbsf57disTQfEY+9japXWav/Ef5Cv04UEW24bTFl2Q4f8xwg=="
     },
-    "testcafe-safe-storage": {
-      "version": "1.1.6",
-      "resolved": "https://registry.npmjs.org/testcafe-safe-storage/-/testcafe-safe-storage-1.1.6.tgz",
-      "integrity": "sha512-WFm1UcmO3uZs+uW8lYtBBJpnrvgTKkMQMKG9BvTEKbjeqhonEXVTxOkGEs3DM1ZB/ylPuwh7Jux7qUtjcM/D2Q=="
-    },
     "testcafe-selector-generator": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/testcafe-selector-generator/-/testcafe-selector-generator-0.1.0.tgz",
diff --git a/package.json b/package.json
index 1c325e60f..f9a87ecd3 100644
--- a/package.json
+++ b/package.json
@@ -62,17 +62,17 @@
   },
   "dependencies": {
     "@babel/core": "^7.23.2",
-    "@babel/plugin-proposal-async-generator-functions": "^7.20.7",
-    "@babel/plugin-proposal-class-properties": "^7.18.6",
     "@babel/plugin-proposal-decorators": "^7.23.2",
-    "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
-    "@babel/plugin-proposal-private-methods": "^7.18.6",
     "@babel/plugin-syntax-dynamic-import": "^7.8.3",
     "@babel/plugin-syntax-import-meta": "^7.10.4",
+    "@babel/plugin-transform-async-generator-functions": "^7.25.4",
     "@babel/plugin-transform-async-to-generator": "^7.22.5",
+    "@babel/plugin-transform-class-properties": "^7.25.4",
     "@babel/plugin-transform-class-static-block": "^7.24.7",
     "@babel/plugin-transform-exponentiation-operator": "^7.22.5",
     "@babel/plugin-transform-for-of": "^7.22.15",
+    "@babel/plugin-transform-object-rest-spread": "^7.24.7",
+    "@babel/plugin-transform-private-methods": "^7.25.4",
     "@babel/plugin-transform-runtime": "7.23.3",
     "@babel/preset-env": "^7.23.2",
     "@babel/preset-flow": "^7.22.15",
@@ -150,7 +150,6 @@
     "testcafe-reporter-minimal": "^2.2.0",
     "testcafe-reporter-spec": "^2.2.0",
     "testcafe-reporter-xunit": "^2.2.1",
-    "testcafe-safe-storage": "^1.1.1",
     "testcafe-selector-generator": "^0.1.0",
     "time-limit-promise": "^1.0.2",
     "tmp": "0.0.28",
diff --git a/src/client/automation/playback/move/event-sequence/base.js b/src/client/automation/playback/move/event-sequence/base.js
index 0b471d736..b4734c3d2 100644
--- a/src/client/automation/playback/move/event-sequence/base.js
+++ b/src/client/automation/playback/move/event-sequence/base.js
@@ -29,6 +29,15 @@ export default class MoveEventSequenceBase {
     }
 
     run (currentElement, prevElement, options, dragElement, dragDataStore) {
+
+        const prevElementInDocument = prevElement && domUtils.isElementInDocument(prevElement);
+
+        const prevElementInRemovedIframe = prevElement && domUtils.isElementInIframe(prevElement) &&
+                                         !domUtils.getIframeByElement(prevElement);
+
+        if (!prevElementInDocument || prevElementInRemovedIframe)
+            prevElement = null;
+
         const elementChanged = currentElement !== prevElement;
         const commonAncestor = elementChanged ? domUtils.getCommonAncestor(currentElement, prevElement) : null;
 
diff --git a/src/compiler/babel/load-libs.js b/src/compiler/babel/load-libs.js
index dcf950512..74cac3c85 100644
--- a/src/compiler/babel/load-libs.js
+++ b/src/compiler/babel/load-libs.js
@@ -67,8 +67,8 @@ export default function loadLibs ({ esm } = {}) {
         presetEnvForTestCode:       [require('@babel/preset-env'), getPresetEnvForTestCodeOpts(esm)],
         moduleResolver:             [require('babel-plugin-module-resolver'), getModuleResolverOpts(esm)],
         presetReact:                getPresetReact(),
-        proposalPrivateMethods:     [require('@babel/plugin-proposal-private-methods'), { loose: true }],
-        proposalClassProperties:    [require('@babel/plugin-proposal-class-properties'), { loose: true }],
+        transformPrivateMethods:    [require('@babel/plugin-transform-private-methods'), { loose: true }],
+        transformClassProperties:   [require('@babel/plugin-transform-class-properties'), { loose: true }],
         transformClassStaticBlock:  require('@babel/plugin-transform-class-static-block'),
     };
 }
diff --git a/src/compiler/babel/preset-stage-2.ts b/src/compiler/babel/preset-stage-2.ts
index 71f91cf42..d30faf32e 100644
--- a/src/compiler/babel/preset-stage-2.ts
+++ b/src/compiler/babel/preset-stage-2.ts
@@ -4,14 +4,14 @@ export default {
             require('babel-plugin-syntax-trailing-function-commas'),
             require('@babel/plugin-transform-async-to-generator'),
             require('@babel/plugin-transform-exponentiation-operator'),
-            require('@babel/plugin-proposal-async-generator-functions'),
-            require('@babel/plugin-proposal-object-rest-spread'),
+            require('@babel/plugin-transform-async-generator-functions'),
+            require('@babel/plugin-transform-object-rest-spread'),
         ],
     }],
     plugins: [
         require('@babel/plugin-syntax-dynamic-import'),
         [require('@babel/plugin-proposal-decorators'), { 'legacy': true }],
-        require('@babel/plugin-proposal-class-properties'),
-        require('@babel/plugin-proposal-async-generator-functions'),
+        require('@babel/plugin-transform-class-properties'),
+        require('@babel/plugin-transform-async-generator-functions'),
     ],
 };
diff --git a/src/native-automation/request-pipeline/safe-api.ts b/src/native-automation/request-pipeline/safe-api.ts
index 86706e69a..c29d0e2f4 100644
--- a/src/native-automation/request-pipeline/safe-api.ts
+++ b/src/native-automation/request-pipeline/safe-api.ts
@@ -16,13 +16,15 @@ const IGNORED_ERROR_CODES = {
     // The "Session not found" error can occur in iframes for unclear reasons.
     SESSION_WITH_GIVEN_ID_NOT_FOUND: -32001,
 };
+//The "WebSocket connection closed" error occurs on closeWindow in multiple windows mode
+const IGNORED_ERROR_MESSAGES = ['WebSocket connection closed'];
 
 export async function connectionResetGuard (handleRequestFn: () => Promise<void>, handleErrorFn: (err: any) => void): Promise<void> {
     try {
         await handleRequestFn();
     }
     catch (err: any) {
-        if (Object.values(IGNORED_ERROR_CODES).includes(err?.response?.code))
+        if (Object.values(IGNORED_ERROR_CODES).includes(err?.response?.code) || IGNORED_ERROR_MESSAGES.includes(err.message))
             return;
 
         handleErrorFn(err);


Test patch:
diff --git a/src/compiler/test-file/formats/es-next/compiler.js b/src/compiler/test-file/formats/es-next/compiler.js
index 523d90736..9d520f806 100644
--- a/src/compiler/test-file/formats/es-next/compiler.js
+++ b/src/compiler/test-file/formats/es-next/compiler.js
@@ -21,14 +21,14 @@ export default class ESNextTestFileCompiler extends APIBasedTestFileCompilerBase
             presetEnvForTestCode,
             presetReact,
             moduleResolver,
-            proposalPrivateMethods,
-            proposalClassProperties,
+            transformPrivateMethods,
+            transformClassProperties,
             transformClassStaticBlock,
         } = loadBabelLibs({ esm });
 
         const opts = Object.assign({}, BASE_BABEL_OPTIONS, {
             presets:    [presetStage2, presetEnvForTestCode, presetReact],
-            plugins:    [transformRuntime, moduleResolver, proposalPrivateMethods, proposalClassProperties, transformClassStaticBlock],
+            plugins:    [transformRuntime, moduleResolver, transformPrivateMethods, transformClassProperties, transformClassStaticBlock],
             sourceMaps: 'inline',
             filename,
         });
diff --git a/test/functional/fixtures/regression/gh-8264/pages/index.html b/test/functional/fixtures/regression/gh-8264/pages/index.html
new file mode 100644
index 000000000..e7481e706
--- /dev/null
+++ b/test/functional/fixtures/regression/gh-8264/pages/index.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+</head>
+<body>
+    <a href="#" id="link">Click me to remove</a>
+    <button id="btn">Button</button>
+
+    <script>
+        document.getElementById('link').addEventListener('click', function(event) {
+            event.preventDefault();
+            this.remove();
+        });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/test/functional/fixtures/regression/gh-8264/test.js b/test/functional/fixtures/regression/gh-8264/test.js
new file mode 100644
index 000000000..29df7409b
--- /dev/null
+++ b/test/functional/fixtures/regression/gh-8264/test.js
@@ -0,0 +1,5 @@
+describe('[Regression](GH-8264)', function () {
+    it('Test should not fails with Uncaught object error on leaveElement when the prevElement is removed from the DOM', function () {
+        return runTests('testcafe-fixtures/index.js', 'Callsite Issue', { only: 'chrome' });
+    });
+});
diff --git a/test/functional/fixtures/regression/gh-8264/testcafe-fixtures/index.js b/test/functional/fixtures/regression/gh-8264/testcafe-fixtures/index.js
new file mode 100644
index 000000000..54420cdd0
--- /dev/null
+++ b/test/functional/fixtures/regression/gh-8264/testcafe-fixtures/index.js
@@ -0,0 +1,9 @@
+
+fixture('GH-8264 - Callsite Issue')
+    .page`http://localhost:3000/fixtures/regression/gh-8264/pages/index.html`;
+
+test('Callsite Issue', async t => {
+    await t
+        .click('#link')
+        .click('#btn');
+});