We're looking to develop an ElectronJS app for particular website automation at our desk job, which includes common tasks like login, form filling, report downloading etc.
We've tried basic tutorial of ElectronJS, Spectron, NightmareJS, Puppeteer etc and all of them work fine separately, but very less documentation (although open github issues) are available on integration of each other.
We want to achieve following:
Login state (session) should not be deleted on ElectronJS app closing and should be available on restart of app.
Few menu buttons which initiates some automation tasks like download, form fill etc on existing browserWindow
We don't need headless automation, where some magic happens behind the scene. We need menu/button click based actions/tasks on current page only.
NightmareJS, Puppeteer etc all seems to start their own instances of web pages (since because they were built for testing of standalone apps) but what we need is automation of existing BrowserWindows.
Is puppeteer or nightmarejs correct tools for such goals? If yes, any documentation?
Or else, should we inject our own native JS events like mouseclick etc events in console to perform action?
You can use puppeteer-core. core version by default does not download Chromium, which you do not need if you want to control an Electron app.
In the test you then call launch method, where you define electron as the executable file instead of Chromium, like in following snippet:
const electron = require("electron");
const puppeteer = require("puppeteer-core");
const delay = ms =>
new Promise(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
(async () => {
try {
const app = await puppeteer.launch({
executablePath: electron,
args: ["."],
headless: false,
});
const pages = await app.pages();
const [page] = pages;
await page.setViewport({ width: 1200, height: 700 });
await delay(5000);
const image = await page.screenshot();
console.log(image);
await page.close();
await delay(2000);
await app.close();
} catch (error) {
console.error(error);
}
})();
Update for electron 5.x.y and up (currently up to 7.x.y, I did not test it on 8.x.y beta yet), where puppeteer.connect is used instead of launch method:
// const assert = require("assert");
const electron = require("electron");
const kill = require("tree-kill");
const puppeteer = require("puppeteer-core");
const { spawn } = require("child_process");
let pid;
const run = async () => {
const port = 9200; // Debugging port
const startTime = Date.now();
const timeout = 20000; // Timeout in miliseconds
let app;
// Start Electron with custom debugging port
pid = spawn(electron, [".", `--remote-debugging-port=${port}`], {
shell: true
}).pid;
// Wait for Puppeteer to connect
while (!app) {
try {
app = await puppeteer.connect({
browserURL: `http://localhost:${port}`,
defaultViewport: { width: 1000, height: 600 } // Optional I think
});
} catch (error) {
if (Date.now() > startTime + timeout) {
throw error;
}
}
}
// Do something, e.g.:
// const [page] = await app.pages();
// await page.waitForSelector("#someid")//
// const text = await page.$eval("#someid", element => element.innerText);
// assert(text === "Your expected text");
// await page.close();
};
run()
.then(() => {
// Do something
})
.catch(error => {
// Do something
kill(pid, () => {
process.exit(1);
});
});
Getting the pid and using kill is optional. For running the script on some CI platform it does not matter, but for local environment you would have to close the electron app manually after each failed try.
Simple demo repo:
https://github.com/peterdanis/electron-puppeteer-demo
Automation Script in Java using Selenium and ChromeDriver
package setUp;
import helper.Constants;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.DesiredCapabilities;
public class Test {
public static void main(String[] args) {
System.setProperty(Constants.WebDriverType, Constants.WebDriverPath + Constants.WindowsDriver);
ChromeOptions opt = new ChromeOptions();
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("chromeOptions", opt);
capabilities.setBrowserName("chrome");
capabilities.setVersion("73.0.3683.121");
ChromeOptions options = new ChromeOptions();
options.merge(capabilities);
options.setBinary("C:\\\\Program Files\\\\Audio\\\\Audio-Configuration\\\\Audio-Configuration.exe");
options.setCapability("chromeOptions", options);
ChromeDriver driver = new ChromeDriver(options);
try {
Thread.sleep(5000);
WebElement webElement = driver.findElement(By.xpath(
"/html/body/app-root/mat-drawer-container/mat-drawer/div/app-bottom-side-nav/div/app-settings-nav/div/div/a/div"));
webElement.click();
} catch (Exception e) {
System.out.println("Exception trace");
System.out.println(e);
}
}
}
Automation Script in JavaScript using Spectron (built on top-of ChromeDriver and WebDriverIO).
const Application = require("spectron").Application;
const path =
"C:/Program Files/Audio/Audio-Configuration/Audio-Configuration.exe";
const myApp = new Application({
path: path,
chromeDriverArgs: ["--disable-extensions"],
env: {
SPECTRON: true,
ELECTRON_ENABLE_LOGGING: true,
ELECTRON_ENABLE_STACK_DUMPING: true
}
});
const windowClick = async app => {
await app.start();
try {
// Identifying by class name
await app.client.click(".ic-setting");
// Identifying by Id
// await app.client.click("#left-btn");
} catch (error) {
// Log any failures
console.error("Test failed", error.message);
}
// Stop the application
await app.stop();
};
windowClick(myApp);
Spectron is the best match for electron build applications.
You will have access to all electron API.we can start and stop your app by spectron only.
We can run both packaged app or with out even packaging.
https://electronjs.org/spectron
You can use Spectron but if you want to look at documentation, Spectron is using webdriverio which has good documentation.
I recommend you to use Spectron because I tried to automate my tests with java-selenium but it fails some of case. If you want to use selenium, write below code to set capabilities to setup electron app to chromedriver.
ChromeOptions options = new ChromeOptions();
options.setBinary(binaryPath);
options.addArguments("--app=" + argPath);
options.setCapability("chromeOptions", options);
driver = new ChromeDriver(options);
Hope this will help to you.
If integrating with electron nightmare is a very good library to achieve this even it will be ready to distribute with it, here is the following useful documentation for the same resource1
and
Related
I just started coding, and I was wondering if there was a way to open multiple tabs concurrently with one another. Currently, my code goes something like this:
const puppeteer = require("puppeteer");
const rand_url = "https://www.google.com";
async function initBrowser() {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto(rand_url);
await page.setViewport({
width: 1200,
height: 800,
});
return page;
}
async function login(page) {
await page.goto("https://www.google.com");
await page.waitFor(100);
await page.type("input[id ='user_login'", "xxx");
await page.waitFor(100);
await page.type("input[id ='user_password'", "xxx");
}
this is not my exact code, replaced with different aliases, but you get the idea. I was wondering if there was anyone out there that knows the code that allows this same exact browser to be opened on multiple instances, replacing the respective login info only. Of course, it would be great to prevent my IP from getting banned too, so if there was a way to apply proxies to each respective "browser"/ instance, that would be perfect.
Lastly, I would like to know whether or not playwright or puppeteer is superior in the way they can handle these multiple instances. I don't even know if this is a possibility, but please enlighten me. I want to learn more.
You can use multiple browser window as different login/cookies.
For simplicity, you can use the puppeteer-cluster module by Thomas Dondorf.
This module can make your puppeteer launched and queued one by one so that you can use this to automating your login, and even save login cookies for the next launches.
Feel free to go to the Github: https://github.com/thomasdondorf/puppeteer-cluster
const { Cluster } = require('puppeteer-cluster')
(async () => {
const cluster = await Cluster.launch({
concurrency: Cluster.CONCURRENCY_CONTEXT,
maxConcurrency: 2, // <= this is the number of
// parallel task running simultaneously
}) // You can change to the number of CPU
const cpuNumber = require('os').cpus().length // for example
await cluster.task(async ({ page, data: [username, password] }) => {
await page.goto('https://www.example.com')
await page.waitForTimeout(100)
await page.type('input[id ="user_login"', username)
await page.waitForTimeout(100)
await page.type('input[id ="user_password"', password)
const screen = await page.screenshot()
// Store screenshot, Save Cookies, do something else
});
cluster.queue(['myFirstUsername', 'PassW0Rd1'])
cluster.queue(['anotherUsername', 'Secr3tAgent!'])
// cluster.queue([username, password])
// username and password array passed into cluster task function
// many more pages/account
await cluster.idle()
await cluster.close()
})()
For Playwright, sadly still unsupported by the module above,you can use browser pool (cluster) module to automating the Playwright launcher.
And for proxy usage, I recommend Puppeteer library as the legendary one.
Don't forget to choose my answer as the right one, if this helps you.
There are profiling and proxy options; you could combine them to achieve your goal:
Profile, https://playwright.dev/docs/api/class-browsertype#browser-type-launch-persistent-context
import { chromium } from 'playwright'
const userDataDir = /tmp/ + process.argv[2]
const browserContext = await chromium.launchPersistentContext(userDataDir)
// ...
Proxy, https://playwright.dev/docs/api/class-browsertype#browser-type-launch
import { chromium } from 'playwright'
const proxy = { /* secret */ }
const browser = await chromium.launch({
proxy: { server: 'pre-context' }
})
const browserContext = await browser.newContext({
proxy: {
server: `http://${proxy.ip}:${proxy.port}`,
username: proxy.username,
password: proxy.password,
}
})
// ...
I am making a chrome extension that requires MetaMask authentication.
I started developing it as a web application, but as a chrome extension, it doesn't detect MetaMask...
This is my web application code right now:
function toggleButton() {
const loginButton = document.getElementById("login-button");
if (!window.ethereum) {
loginButton.innerText = "Install MetaMask";
loginButton.addEventListener("click", () => {
window.open("https://metamask.io");
});
return false;
}
loginButton.addEventListener("click", loginWithMetaMask);
}
async function loginWithMetaMask() {
const accounts = await window.ethereum.request({ method: "eth_requestAccounts" }).catch((e) => {
console.error(e.message);
return;
});
if (!accounts) {
return;
}
const userWallet = document.getElementById("user-wallet");
userWallet.innerText = accounts[0];
}
window.addEventListener("DOMContentLoaded", toggleButton);
Is there a way to migrate the web application to a chrome extension?
This is how to do it.
Install metamask-extension-provider in your project
https://github.com/MetaMask/extension-provider
You will need to use browserify to compile your plugin.
Create a custom connector like this
class customConnector extends Moralis.AbstractWeb3Connector {
async activate() {
const provider = createMetaMaskProvider();
if (!provider) {
console.error("MetaMask provider not detected.");
throw new Error("MetaMask provider not detected.");
}
const [accounts, chainId] = await Promise.all([
provider.request({
method: 'eth_requestAccounts',
}),
provider.request({ method: 'eth_chainId' }),
]);
const account = accounts[0] ? accounts[0].toLowerCase() : null;
this.chainId = provider.chainId;
this.account = provider.selectedAddress;
this.provider = provider;
this.subscribeToEvents(provider);
return { provider, chainId, account };
}
}
Now you can authenticate using MetaMask like this
Moralis.authenticate({ connector: customConnector })
I have created a small boilerplate of chrome extension with react and added metamask login and web3 support.
https://github.com/shaheem-khanzada/chrome-extension-react-metamask-boilerplate
so the problem is we cannot access window.eth... directly in the extension that is why metamask created a module called metamask-extension-provider but in order to run this we need to use browserify
compiler so in simple words, we need to write our own compiler that's why I created this boilerplate to help others it uses gulp to create task and then bundle files using browserify and babelify
I have a list of urls that need to be scraped from a website that uses React, for this reason I am using Puppeteer.
I do not want to be blocked by anti-bot servers, for this reason I have added puppeteer-extra-plugin-stealth
I want to prevent ads from loading on the pages, so I am blocking ads by using puppeteer-extra-plugin-adblocker
I also want to prevent my IP address from being blacklisted, so I have used TOR nodes to have different IP addresses.
Below is a simplified version of my code and the setup works (TOR_port and webUrl are assigned dynamically though but for simplifying my question I have assigned it as a variable) .
There is a problem though:
const puppeteer = require('puppeteer-extra');
const _StealthPlugin = require('puppeteer-extra-plugin-stealth');
const _AdblockerPlugin = require('puppeteer-extra-plugin-adblocker');
puppeteer.use(_StealthPlugin());
puppeteer.use(_AdblockerPlugin());
var TOR_port = 13931;
var webUrl ='https://www.zillow.com/homedetails/2861-Bass-Haven-Ln-Saint-Augustine-FL-32092/47739703_zpid/';
const browser = await puppeteer.launch({
dumpio: false,
headless: false,
args: [
`--proxy-server=socks5://127.0.0.1:${TOR_port}`,
`--no-sandbox`,
],
ignoreHTTPSErrors: true,
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 720 });
await page.goto(webUrl, {
waitUntil: 'load',
timeout: 30000,
});
page
.waitForSelector('.price')
.then(() => {
console.log('The price is available');
await browser.close();
})
.catch(() => {
// close this since it is clearly not a zillow website
throw new Error('This is not the zillow website');
});
} catch (e) {
await browser.close();
}
The above setup works but is very unreliable and I recently learnt about Puppeteer-Cluster. I need it to help me manage crawling multiple pages, to track my scraping tasks.
So, my question is how do I implement Puppeteer-Cluster with the above set-up. I am aware of an example(https://github.com/thomasdondorf/puppeteer-cluster/blob/master/examples/different-puppeteer-library.js) offered by the library to show how you can implement plugins, but is so bare that I didn't quite understand it.
How do I implement Puppeteer-Cluster with the above TOR, AdBlocker, and Stealth configurations?
You can just hand over your puppeteer Instance like following:
const puppeteer = require('puppeteer-extra');
const _StealthPlugin = require('puppeteer-extra-plugin-stealth');
const _AdblockerPlugin = require('puppeteer-extra-plugin-adblocker');
puppeteer.use(_StealthPlugin());
puppeteer.use(_AdblockerPlugin());
const browser = await puppeteer.launch({
puppeteer,
});
Src: https://github.com/thomasdondorf/puppeteer-cluster#clusterlaunchoptions
You can just add the plugins with puppeteer.use()
You have to use puppeteer-extra.
const { addExtra } = require("puppeteer-extra");
const vanillaPuppeteer = require("puppeteer");
const StealthPlugin = require("puppeteer-extra-plugin-stealth");
const RecaptchaPlugin = require("puppeteer-extra-plugin-recaptcha");
const { Cluster } = require("puppeteer-cluster");
(async () => {
const puppeteer = addExtra(vanillaPuppeteer);
puppeteer.use(StealthPlugin());
puppeteer.use(RecaptchaPlugin());
// Do stuff
})();
I'm trying to add in the default download path with chrome capabilities using the code shown below:
const test = async () => {
let builder = await new Builder().forBrowser("chrome");
let chromeCapabilities = builder.getCapabilities();
// chromeCapabilities.set("download.default_directory", downloadFolder);
chromeCapabilities.merge({ "download.default_directory": downloadFolder });
console.log(chromeCapabilities.get("download.default_directory"));
// builder.setChromeOptions(chromeCapabilities);
builder.withCapabilities(chromeCapabilities);
// builder.setChromeOptions({ "download.default_directory": downloadFolder });
// builder.withCapabilities({ "download.default_directory": downloadFolder });
console.log(builder.getCapabilities().get("download.default_directory"));
const driver = builder.build();
await driver.get(tempoboxLoginUrl);
const driverCapabilities = await (await driver).getCapabilities();
console.log(await driverCapabilities.get("download.default_directory"));
};
test();
After merging the capabilities with the new capability I want to add, I can log the value of the capability and it shows as expected. However once the driver is built, logging the value of the capability returns undefined. The following is the output when running this code with Node:
> node Test.js
C:\Users\dummy\Desktop << first log
C:\Users\dummy\Desktop << second log
DevTools listening on ws://127.0.0.1:57980/devtools/browser/d53946e4-cedc-4809-a20c-b8b3416463cc
undefined << third log
So I eventually figured it out. The capabilities must be added in a very specific way. See example here:
const builder = new Builder().withCapabilities({
browserName: "chrome",
"goog:chromeOptions": {
args: ["--start-maximized"],
prefs: { "download.default_directory": downloadFolder },
},
});
const driver = await builder.build();
I have to test native android app, with test written in js.
I cannot find working example of this concept.
import { AndroidDriver } from "appium-android-driver"
This import throw many errors, I am looking for a hint - working simple project.
This way of importing works with latest version of Node JS. Make sure your using the latest version. The other work around is to use the legacy way
var AndroidDriver = require('appium-android-driver').AndroidDriver;
Hope this helps.
You can use wd.js as Node.js client for Appium:
Taking in account you are familiar with async/await concept, the code may look like:
const wd = require('wd')
const serverConfig = {
host: 'localhost',
port: 4723
}
const desired = {
platformName: 'Android',
deviceName: 'Android Emulator',
appWaitActivity: <activity to wait>,
appWaitPackage: <app package name>,
androidInstallTimeout: 90000,
app: <path to app>
}
const driver = wd.promiseChainRemote(serverConfig)
await driver.init(desired)
await driver.setImplicitWaitTimeout(5000)
await driver.elementById('el_id').click()
There is no need to require wd as it is automatically imported from 'nativescript-dev-appium'
It looks like you are using typescript. Here is a typescript solution:
import { AppiumDriver, createDriver, SearchOptions,
Direction, nsCapabilities } from "nativescript-dev-appium";
import { assert } from "chai";
const addContext = require('mochawesome/addContext');
Then in the same file instantiate a driver instance and use that instance:
describe('My Suite', () => {
let driver: AppiumDriver
before(async () => {
// Wait for the driver instance to be created
driver = await createDriver()
})
after(async () => {
// Destroy the driver instance
await driver.quit()
})
it('validates something', async () => {
// write your test in here
})
})
Go here for some great documentation