Skip to content

Commit e242054

Browse files
authored
feat: Add Opera support (#61)
1 parent 1b71b3d commit e242054

File tree

4 files changed

+165
-4
lines changed

4 files changed

+165
-4
lines changed

.github/workflows/test-gha.yaml

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,23 @@ jobs:
3535
node-version: 22
3636
registry-url: 'https://registry.npmjs.org'
3737

38-
# Firefox and Edge might be missing on Mac CI images.
39-
- name: 'Install Firefox & Edge on Mac'
38+
# Firefox, Edge and Opera might be missing on Mac CI images.
39+
- name: 'Install Firefox & Edge & Opera on Mac'
4040
timeout-minutes: 5
4141
if: matrix.os == 'macos-latest'
42-
run: brew install --cask firefox microsoft-edge
42+
run: brew install --cask firefox microsoft-edge opera
43+
44+
# Opera might be missing on Windows CI images.
45+
- name: 'Install Opera on Windows'
46+
timeout-minutes: 5
47+
if: matrix.os == 'windows-latest'
48+
run: choco install -y opera --ignore-checksums --params '"/NoAutostart /NoDesktopShortcut /NoTaskbarShortcut"'
49+
50+
# Opera might be missing on Ubuntu CI images.
51+
- name: 'Install Opera on Ubuntu'
52+
timeout-minutes: 5
53+
if: matrix.os == 'ubuntu-latest'
54+
run: sudo apt -y update && sudo apt -y install snapd && sudo snap install opera
4355

4456
- run: npm ci
4557

@@ -61,13 +73,16 @@ jobs:
6173
check_driver chromedriver || rv=1
6274
check_driver geckodriver || rv=1
6375
check_driver msedgedriver || rv=1
76+
check_driver operadriver || rv=1
6477
elif [[ "${{ runner.os }}" == "Linux" ]]; then
6578
check_driver chromedriver || rv=1
6679
check_driver geckodriver || rv=1
80+
check_driver operadriver || rv=1
6781
elif [[ "${{ runner.os }}" == "Windows" ]]; then
6882
check_driver chromedriver.exe || rv=1
6983
check_driver geckodriver.exe || rv=1
7084
check_driver msedgedriver.exe || rv=1
85+
check_driver operadriver.exe || rv=1
7186
fi
7287
exit "$rv"
7388

main.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ const {ChromeWebDriverInstaller} = require('./chrome.js');
99
const {ChromeAndroidWebDriverInstaller} = require('./chrome-android.js');
1010
const {FirefoxWebDriverInstaller} = require('./firefox.js');
1111
const {EdgeWebDriverInstaller} = require('./edge.js');
12+
const {OperaWebDriverInstaller} = require('./opera.js');
1213

1314
const INSTALLER_CLASSES = [
1415
ChromeWebDriverInstaller,
1516
ChromeAndroidWebDriverInstaller,
1617
FirefoxWebDriverInstaller,
1718
EdgeWebDriverInstaller,
19+
OperaWebDriverInstaller,
1820
];
1921

2022
/**

opera.js

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*! @license
2+
* WebDriver Installer
3+
* Copyright 2022 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
const {WebDriverInstallerBase} = require('./base.js');
8+
const {InstallerUtils} = require('./utils.js');
9+
10+
const os = require('os');
11+
const path = require('path');
12+
13+
class OperaWebDriverInstaller extends WebDriverInstallerBase {
14+
getBrowserName() {
15+
return 'Opera';
16+
}
17+
18+
getDriverName() {
19+
return 'operadriver';
20+
}
21+
22+
/**
23+
* Get installed Opera browser version.
24+
* Handles macOS, Linux (including Snap), and Windows.
25+
*/
26+
async getInstalledBrowserVersion() {
27+
if (os.platform() === 'darwin') {
28+
// Try common macOS bundle names
29+
return (
30+
await InstallerUtils.getMacAppVersion('Opera') ||
31+
await InstallerUtils.getMacAppVersion('Opera Stable')
32+
);
33+
}
34+
35+
if (os.platform() === 'linux') {
36+
// Try Snap installation first
37+
let version = await InstallerUtils.getCommandOutputOrNullIfMissing(
38+
['/snap/bin/opera', '--version']
39+
);
40+
if (!version) {
41+
// Fallback to PATH
42+
version = await InstallerUtils.getCommandOutputOrNullIfMissing(
43+
['opera', '--version']
44+
);
45+
}
46+
// return only version number
47+
return version ? version.trim().split(' ')[0] : null;
48+
}
49+
50+
if (os.platform() === 'win32') {
51+
// Try standard paths
52+
const possiblePaths = [
53+
'C:\\Program Files\\Opera\\opera.exe',
54+
'C:\\Program Files (x86)\\Opera\\opera.exe',
55+
'C:\\Users\\' + os.userInfo().username + '\\AppData\\Local\\Programs\\Opera\\opera.exe',
56+
];
57+
58+
for (const exePath of possiblePaths) {
59+
const version = await InstallerUtils.getWindowsExeVersion(exePath);
60+
if (version) {
61+
return version;
62+
}
63+
}
64+
65+
// Fallback to PATH
66+
return await InstallerUtils.getWindowsExeVersion('opera.exe');
67+
}
68+
69+
throw new Error(`Unsupported platform: ${os.platform()}`);
70+
}
71+
72+
/**
73+
* Get installed OperaDriver version from output directory.
74+
*/
75+
async getInstalledDriverVersion(outputDirectory) {
76+
let outputPath = path.join(outputDirectory, this.getDriverName());
77+
if (os.platform() === 'win32') {
78+
outputPath += '.exe';
79+
}
80+
81+
const output = await InstallerUtils.getCommandOutputOrNullIfMissing(
82+
[outputPath, '--version']
83+
);
84+
// Example: "operadriver 114.0.5735.90 (...)"
85+
return output ? output.trim().split(' ')[1] : null;
86+
}
87+
88+
/**
89+
* Always fetch the latest OperaDriver release from GitHub.
90+
* This avoids 404 errors if the installed Opera version
91+
* does not have a matching driver.
92+
*/
93+
async getBestDriverVersion(_browserVersion) {
94+
const tag = await InstallerUtils.fetchLatestGitHubTag(
95+
'operasoftware/operachromiumdriver'
96+
);
97+
98+
// tag example: "v114.0.5735.90"
99+
return tag.replace(/^v\.?/, '');
100+
}
101+
102+
/**
103+
* Install the OperaDriver binary for the current platform.
104+
*/
105+
async install(driverVersion, outputDirectory) {
106+
let platform;
107+
let binaryName = 'operadriver';
108+
109+
if (os.platform() === 'linux') {
110+
platform = 'linux64';
111+
} else if (os.platform() === 'darwin') {
112+
platform = 'mac64';
113+
} else if (os.platform() === 'win32') {
114+
platform = 'win64';
115+
binaryName += '.exe';
116+
} else {
117+
throw new Error(`Unsupported platform: ${os.platform()}`);
118+
}
119+
120+
const archiveUrl =
121+
`https://github.com/operasoftware/operachromiumdriver/releases/download/` +
122+
`v.${driverVersion}/operadriver_${platform}.zip`;
123+
124+
// IMPORTANT: operadriver is inside a folder in the zip
125+
const nameInArchive = `operadriver_${platform}/${binaryName}`;
126+
127+
let outputName = this.getDriverName();
128+
if (os.platform() === 'win32') {
129+
outputName += '.exe';
130+
}
131+
132+
// Install binary from network archive
133+
return InstallerUtils.installBinary(
134+
archiveUrl,
135+
nameInArchive,
136+
outputName,
137+
outputDirectory,
138+
/* isZip */ true
139+
);
140+
}
141+
}
142+
143+
module.exports = {OperaWebDriverInstaller};

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"webdriver",
1515
"chrome",
1616
"firefox",
17-
"edge"
17+
"edge",
18+
"opera"
1819
],
1920
"author": "Joey Parrish <joeyparrish@google.com>",
2021
"dependencies": {

0 commit comments

Comments
 (0)