Repository: codeceptjs/CodeceptJS (js)

Issue:
codeceptjs/puppeteer@14.4.1 fails on handleDownloads()
#### What are you trying to achieve?
Use handleDownloads() to download file in test with codeceptjs/puppeteer

#### What do you get instead?
On I.handleDownloads() there is the following error:
```
     this.page._client.send is not a function
      at Puppeteer.handleDownloads (node_modules/codeceptjs/lib/helper/Puppeteer.js:1072:30)
      at Step.run (node_modules/codeceptjs/lib/step.js:122:47)
      at /content/n/d3/node_modules/codeceptjs/lib/actor.js:134:23

```
### Details

* CodeceptJS version: 3.3.3
* NodeJS Version: 16.15.1
* Operating System: Linux (WSL2)
* puppeteer: 14.4.1
* Configuration file:
```js
{
  tests: ['./test/*.js', './test/testdir/*.js'],
  output: './test/output',
  helpers: {
    Puppeteer: {
      url: 'http://localhost:8080',
      show: true,
      windowSize: '1200x900',
      chrome: {
        args: ['--no-sandbox', '--window-size=1280,960'],
        defaultViewport: {
          width: 1280,
          height: 960
        }
      }
    },
    FileSystem: {},
    clickIfVisible: {
      require: './test/helpers/click-if-visible.js',
    },
  },
  include: {
    I: './steps_file.js'
  },
  bootstrap: null,
  mocha: {},
  name: 'LAP',
  plugins: {
    pauseOnFail: {},
    retryFailedStep: {
      enabled: true
    },
    screenshotOnFail: {
      enabled: true
    }
  }
}
```

My test worked fine and now it fails on 'I.handleDownloads() with the error shown above.
All I changed was the puppeteer version.

Everything is fine with puppeteer@14.3.0 (and previous versions).
Since puppeteer@14.4.0 (including current 14.4.1) the test always fails.

Pull request description:
fix(3341): handleDownloads function is broken with Puppeteer 14.4 and onwards
## Motivation/Description of the PR
- Resolves #3341 

Applicable helpers:
- [x] Puppeteer

## Type of change
- [x] :bug: Bug fix

## Checklist:
- [x] Lint checking (Run `npm run lint`)
- [x] Local tests are passed (Run `npm test`)


Patch:
diff --git a/docs/community-helpers.md b/docs/community-helpers.md
index ef758229..4c56ab8c 100644
--- a/docs/community-helpers.md
+++ b/docs/community-helpers.md
@@ -37,6 +37,7 @@ Please **add your own** by editing this page.
 * [codeceptjs-rphelper](https://github.com/reportportal/agent-js-codecept) is a CodeceptJS helper which can publish tests results on ReportPortal after execution.
 * [codeceptjs-xray-helper](https://www.npmjs.com/package/codeceptjs-xray-helper) is a CodeceptJS helper which can publish tests results on [XRAY](https://confluence.xpand-it.com/display/XRAYCLOUD/Import+Execution+Results+-+REST).
 * [codeceptjs-slack-reporter](https://www.npmjs.com/package/codeceptjs-slack-reporter) Get a Slack notification when one or more scenarios fail.
+* [codeceptjs-tesults](https://www.npmjs.com/package/codeceptjs-tesults) Report test results data from CodeceptJS to Tesults with this plugin.
 
 ## Page Object Code Generator
 * [codeceptjs-CodeGenerator](https://github.com/senthillkumar/CodeCeptJS-PageObject) is a CodeceptJS custom wrapper which can create page class with action methods from the page object file(JSON) and project setup(Folder Structure).
diff --git a/docs/reports.md b/docs/reports.md
index 4fb98a19..36a9524b 100644
--- a/docs/reports.md
+++ b/docs/reports.md
@@ -435,3 +435,14 @@ npm i codeceptjs-testrail --save
 
 Now there is new feature, add the configuration to test run of test plan
 ![Attachemnt for failed case](http://g.recordit.co/uQLvQUq7cT.gif)
+
+
+## Tesults
+
+Submit test results data from CodeceptJS to [Tesults](https://www.tesults.com) easily with the [codeceptjs-tesults](https://www.npmjs.com/package/codeceptjs-tesults) plugin. Test results data is submitted automatically after a test run completes.
+
+```sh
+npm i codeceptjs-tesults --save
+```
+
+Once installed, follow the [quick and easy integration instructions](https://www.tesults.com/docs/codeceptjs) to get setup in no time.
diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js
index f818d51f..29d661e2 100644
--- a/lib/helper/Puppeteer.js
+++ b/lib/helper/Puppeteer.js
@@ -192,7 +192,6 @@ class Puppeteer extends Helper {
     super(config);
 
     puppeteer = requireWithFallback('puppeteer', 'puppeteer-core');
-
     // set defaults
     this.isRemoteBrowser = false;
     this.isRunning = false;
@@ -372,22 +371,22 @@ class Puppeteer extends Helper {
   }
 
   /**
-  * Use Puppeteer API inside a test.
-  *
-  * First argument is a description of an action.
-  * Second argument is async function that gets this helper as parameter.
-  *
-  * { [`page`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-page), [`browser`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-browser) } from Puppeteer API are available.
-  *
-  * ```js
-  * I.usePuppeteerTo('emulate offline mode', async ({ page }) {
-  *   await page.setOfflineMode(true);
-  * });
-  * ```
-  *
-  * @param {string} description used to show in logs.
-  * @param {function} fn async function that is executed with Puppeteer as argument
-  */
+   * Use Puppeteer API inside a test.
+   *
+   * First argument is a description of an action.
+   * Second argument is async function that gets this helper as parameter.
+   *
+   * { [`page`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-page), [`browser`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-browser) } from Puppeteer API are available.
+   *
+   * ```js
+   * I.usePuppeteerTo('emulate offline mode', async ({ page }) {
+   *   await page.setOfflineMode(true);
+   * });
+   * ```
+   *
+   * @param {string} description used to show in logs.
+   * @param {function} fn async function that is executed with Puppeteer as argument
+   */
   usePuppeteerTo(description, fn) {
     return this._useTo(...arguments);
   }
@@ -1069,7 +1068,12 @@ class Puppeteer extends Helper {
       fs.mkdirSync(downloadPath, '0777');
     }
     fsExtra.emptyDirSync(downloadPath);
-    return this.page._client.send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath });
+
+    try {
+      return this.page._client.send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath });
+    } catch (e) {
+      return this.page._client().send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath });
+    }
   }
 
   /**
diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js
index f7d91946..8e783d89 100644
--- a/lib/helper/WebDriver.js
+++ b/lib/helper/WebDriver.js
@@ -567,6 +567,9 @@ class WebDriver extends Helper {
       });
     }
 
+    if (this.browser.capabilities && this.browser.capabilities.platformName) {
+      this.browser.capabilities.platformName = this.browser.capabilities.platformName.toLowerCase();
+    }
     return this.browser;
   }
 
diff --git a/lib/utils.js b/lib/utils.js
index 75ea0c0a..31904cc9 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -33,12 +33,7 @@ const isAsyncFunction = module.exports.isAsyncFunction = function (fn) {
 };
 
 module.exports.fileExists = function (filePath) {
-  try {
-    fs.statSync(filePath);
-  } catch (err) {
-    if (err.code === 'ENOENT') return false;
-  }
-  return true;
+  return fs.existsSync(filePath);
 };
 
 module.exports.isFile = function (filePath) {
diff --git a/package.json b/package.json
index 73974b87..679cccab 100644
--- a/package.json
+++ b/package.json
@@ -90,7 +90,7 @@
     "uuid": "^8.3.2"
   },
   "devDependencies": {
-    "@codeceptjs/detox-helper": "^1.0.2",    
+    "@codeceptjs/detox-helper": "^1.0.2",
     "@codeceptjs/mock-request": "^0.3.1",
     "@faker-js/faker": "^5.5.3",
     "@pollyjs/adapter-puppeteer": "^5.1.0",


Test patch:
diff --git a/test/unit/utils_test.js b/test/unit/utils_test.js
index 90c08480..a9e17d33 100644
--- a/test/unit/utils_test.js
+++ b/test/unit/utils_test.js
@@ -7,8 +7,9 @@ const utils = require('../../lib/utils');
 
 describe('utils', () => {
   describe('#fileExists', () => {
-    it('exists', () => expect(utils.fileExists(__filename)));
-    it('not exists', () => expect(!utils.fileExists('not_utils.js')));
+    it('exists', () => expect(utils.fileExists(__filename)).to.be.true);
+    it('not exists', () => expect(utils.fileExists('not_utils.js')).to.be.false);
+    it('not exists if file used as directory', () => expect(utils.fileExists(`${__filename}/not_utils.js`)).to.be.false);
   });
   /* eslint-disable no-unused-vars */
   describe('#getParamNames', () => {