Skip to content

Commit

Permalink
feat: Add geckodriver download script (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Sep 18, 2024
1 parent d99863e commit 9f3fa7d
Show file tree
Hide file tree
Showing 6 changed files with 424 additions and 10 deletions.
9 changes: 2 additions & 7 deletions .github/workflows/functional-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,6 @@ jobs:
name: Start X virtual frame buffer
- run: sudo apt-get install firefox
name: Install Firefox
- run: |
latest_release="$(curl https://api.github.com/repos/mozilla/geckodriver/releases/latest | jq -r .name)"
curl -L "https://github.com/mozilla/geckodriver/releases/download/v${latest_release}/geckodriver-v${latest_release}-linux64.tar.gz" \
--output /tmp/geckodriver.tar.gz
tar -xzf /tmp/geckodriver.tar.gz
sudo mv geckodriver /usr/bin/
name: Deploy Gecko driver
- run: |
npm install -g appium
npm install
Expand All @@ -50,6 +43,8 @@ jobs:
pushd "$cwd"
cd ~
appium driver install --source=local "$cwd"
appium driver run gecko install-geckodriver
export PATH="$PATH:/usr/local/bin"
nohup appium server \
--port=$APPIUM_TEST_SERVER_PORT \
--address=$APPIUM_TEST_SERVER_HOST \
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ Under the hood this driver is a wrapper/proxy over `geckodriver` binary. Check t

It is mandatory to have both Firefox browser installed and the geckodriver binary downloaded on the platform where automated tests are going to be executed. Firefox could be downloaded from the [official download site](https://www.mozilla.org/en-GB/firefox/all/) and the driver binary could be retrieved from the GitHub [releases page](https://github.com/mozilla/geckodriver/releases). The binary must be put into one of the folders included to PATH environment variable. On macOS it also might be necessary to run `xattr -cr "<binary_path>"` to avoid [notarization](https://firefox-source-docs.mozilla.org/testing/geckodriver/Notarization.html) issues.

Since driver version 1.4.0 the geckodriver binary deployment could be automated via the
[install-geckodriver](#install-geckodriver) driver script.

Then you need to decide where the automated test is going to be executed. Gecko driver supports the following target platforms:
- macOS
- Windows
Expand Down Expand Up @@ -56,6 +59,21 @@ setWindowRect | See https://www.w3.org/TR/webdriver/#capabilities
timeouts | See https://www.w3.org/TR/webdriver/#capabilities
unhandledPromptBehavior | See https://www.w3.org/TR/webdriver/#capabilities

## Scripts

### install-geckodriver

This script is used to install the given or latest stable version of Geckodriver server from
the [GitHub releases](https://github.com/mozilla/geckodriver/release) page.
Run `appium driver run gecko install-geckodriver <optional_version>`, where `optional_version`
must be either valid Geckodriver version number or should not be present (the latest stable version is used then).
By default, the script will download and unpack the binary into `/usr/local/bin/geckodriver`
on macOS and Linux or into `%LOCALAPPDATA%\Mozilla\geckodriver.exe` on Windows.
You must also make sure the `%LOCALAPPDATA%\Mozilla` (Windows) or `/usr/local/bin/` (Linux & macOS)
folder is present in the PATH environment variable before
starting an actual automation session. The deployment script should also show a warning message if
it is unable to find the parent folder in the PATH folders list.

## Example

```python
Expand Down
2 changes: 1 addition & 1 deletion lib/logger.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { logger } from 'appium/support';

const log = logger.getLogger('GeckoDriver');
export const log = logger.getLogger('GeckoDriver');

export default log;
100 changes: 98 additions & 2 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import _ from 'lodash';
import { fs, net, zip, tempDir } from 'appium/support';
import tar from 'tar-stream';
import zlib from 'node:zlib';
import B from 'bluebird';
import path from 'node:path';

const GECKO_CAP_PREFIXES = ['moz:'];
// https://www.w3.org/TR/webdriver/#capabilities
Expand All @@ -13,7 +18,7 @@ const STANDARD_CAPS = [
'unhandledPromptBehavior',
];

function formatCapsForServer (caps) {
export function formatCapsForServer (caps) {
const result = {};
if (caps.browserName) {
result.browserName = 'firefox';
Expand All @@ -30,4 +35,95 @@ function formatCapsForServer (caps) {
return result;
}

export { formatCapsForServer };
/**
*
* @param {string} srcUrl
* @param {string} dstPath
* @returns {Promise<void>}
*/
export async function downloadToFile(srcUrl, dstPath) {
await net.downloadFile(srcUrl, dstPath);
}

/**
*
* @param {string} p
* @returns {Promise<void>}
*/
export async function mkdirp(p) {
await fs.mkdirp(p);
}

/**
*
* @param {string} srcAcrhive
* @param {string} fileToExtract
* @param {string} dstPath
* @returns {Promise<void>}
*/
export async function extractFileFromTarGz(srcAcrhive, fileToExtract, dstPath) {
const chunks = [];
const extract = tar.extract();
const extractPromise = new B((resolve, reject) => {
extract.on('entry', (header, stream, next) => {
if (header.name === fileToExtract) {
stream.on('data', (chunk) => {
chunks.push(chunk);
});
}
stream.on('end', function() {
next();
});
stream.resume();
});
extract.once('error', reject);
extract.once('finish', async () => {
if (chunks.length) {
try {
await fs.writeFile(dstPath, Buffer.concat(chunks));
} catch (e) {
return reject(e);
}
} else {
return reject(
new Error(`The file '${fileToExtract}' could not be found in the '${srcAcrhive}' archive`)
);
}
resolve();
});
});

fs.createReadStream(srcAcrhive)
.pipe(zlib.createGunzip())
.pipe(extract);

await extractPromise;
}

/**
*
* @param {string} srcAcrhive
* @param {string} fileToExtract
* @param {string} dstPath
* @returns {Promise<void>}
*/
export async function extractFileFromZip(srcAcrhive, fileToExtract, dstPath) {
let didFindEntry = false;
await zip.readEntries(srcAcrhive, async ({entry, extractEntryTo}) => {
if (didFindEntry || entry.fileName !== fileToExtract) {
return;
}
didFindEntry = true;

const tmpRoot = await tempDir.openDir();
try {
await extractEntryTo(tmpRoot);
await fs.mv(path.resolve(tmpRoot, entry.fileName), dstPath);
} finally {
await fs.rimraf(tmpRoot);
}
});
if (!didFindEntry) {
throw new Error(`The file '${fileToExtract}' could not be found in the '${srcAcrhive}' archive`);
}
}
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,38 @@
"Android",
"Windows"
],
"scripts": {
"install-geckodriver": "./scripts/install-geckodriver.mjs"
},
"mainClass": "GeckoDriver"
},
"main": "./build/index.js",
"bin": {},
"directories": {
"lib": "lib"
},
"files": [
"index.js",
"lib",
"build",
"CHANGELOG.md",
"LICENSE",
"npm-shrinkwrap.json",
"scripts/*.mjs"
],
"peerDependencies": {
"appium": "^2.4.1"
},
"dependencies": {
"appium-adb": "^12.0.3",
"asyncbox": "^3.0.0",
"axios": "^1.7.7",
"bluebird": "^3.5.1",
"lodash": "^4.17.4",
"portscanner": "2.2.0",
"semver": "^7.6.3",
"source-map-support": "^0.x",
"tar-stream": "^3.1.7",
"teen_process": "^2.0.0"
},
"scripts": {
Expand Down
Loading

0 comments on commit 9f3fa7d

Please sign in to comment.