Cypress browser support - node.js

In the default cypress framework, we have plugins.js -> index.js the below code, and they've listed only chromium and electron browsers. Do we need to add more or is this okay the way it is?
From this picture, it looks like cypress supports only chromium and electron? But while we run 99% of cases we run it in Chrome. It is by default choosing chrome.
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
const cucumber = require("cypress-cucumber-preprocessor").default;
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* #type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
require("cypress-mochawesome-reporter/plugin")(on);
};
module.exports = (on, config) => {
on("file:preprocessor", cucumber());
// cypress/plugins/index.js
on("before:browser:launch", (browser = {}, launchOptions) => {
console.log(launchOptions.args);
if (browser.family === "chromium" && browser.name !== "electron") {
launchOptions.args.push("--start-fullscreen");
launchOptions.args.push("--window-size=1400,1200");
return launchOptions;
}
if (browser.name === "electron") {
launchOptions.preferences.fullscreen = true;
return launchOptions;
}
});
module.exports = (on, config) => {
on("before:browser:launch", (browser, launchOptions) => {
if (browser.name === "chrome" && browser.isHeadless) {
launchOptions.args.push("--disable-gpu");
return launchOptions;
}
});
};
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
};

In the top right corner of the cypress window you should be able to choose which browser you want to run.
You can also specify which browser you want to use in that index.js file. Here is an example:
module.exports = (on, config) => {
// inside config.browsers array each object has information like
// {
// name: 'chrome',
// channel: 'canary',
// family: 'chromium',
// displayName: 'Canary',
// version: '80.0.3966.0',
// path:
// '/Applications/Canary.app/Contents/MacOS/Canary',
// majorVersion: 80
// }
return {
browsers: config.browsers.filter((b) => b.family === 'chromium'),
}
}
See cypress docs for more: https://docs.cypress.io/guides/guides/launching-browsers#Customize-available-browsers

Related

NodeJS contextBridge receiving results from index.js

I've been able to use the preload.js for sending message to API to get things done. I'm able to get responses just fine from iPC, but I'm not able to relay the responses from iPC back to the renderer and I don't understand what I'm missing.
index.js (main)
// Modules to control application life and create native browser window
const { app, BrowserWindow, remote, ipcMain } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
let mainWindow;
const createWindow = () => {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
// and load the index.html of the app.
mainWindow.loadFile('index.html');
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready',() => {
createWindow();
require('./request.js')(mainWindow);
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
request.js - this is used in index/main thread above to process API requests, in this case requests to winax which I use with a separate 32 bit nodeJs interpreter because I couldn't get it to build for use with the same version of Node I can use for electron
const { ipcMain, ipcRenderer } = require('electron');
const { spawn } = require('child_process');
module.exports = function(mainWindow){
let winax;
ipcMain.on('winax', (event,arguments) => {
console.log('winax=');
console.log(arguments);
if(winax === undefined) {
console.log('spawning winax');
winax = spawn(
'C:\\Program Files\\nvm\\v14.20.0\\node.exe',
[ 'winax_microamp/index.js' ], {
shell: false,
stdio: ['inherit', 'inherit', 'inherit', 'ipc' ],
windowsHide: true
}
);
}
console.log('sending winax arguments');
/*
arguments = {
method: 'functionToRun',
owner: 'requestingProcess',
params: { required_params ... }
}
*/
winax.send(arguments);
winax.once('message', (message) => {
console.log('winax response=')
console.log(message);
mainWindow.webContents.postMessage('response', message);
});
});
}
preload.js
'use strict';
// preload.js
// All the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
// MAS: This function executes when the DOM is loaded so we should be able to add button interactions here
const { contextBridge, ipcRenderer } = require('electron');
let testAccDb = 'C:\\Users\\PZYVC7\\OneDrive - Ally Financial\\Access\\electron_microamp.accdb';
//const spawn = require('child_process').spawn
contextBridge.exposeInMainWorld(
'request', {
send: (func) => {
console.log('request.send.func=');
console.log(func);
if(func == 'openStagingTest'){
let args = {
method: 'requestStagingDatabase',
owner: 'ui',
params: {
path: testAccDb
}
};
ipcRenderer.send('winax',args);
} else if(func == 'closeStagingTest') {
let args = {
method: 'closeStagingDatabase',
owner: 'ui'
};
ipcRenderer.send('winax',args);
}
},
response: (message) => {
ipcRenderer.on('message', (message) => {
console.log('winax reply=');
console.log(message);
});
}
}
);
window.addEventListener('DOMContentLoaded', () => {
/*const replaceText = (selector, text) => {
const element = document.getElementById(selector);
if (element) element.innerText = text;
}
for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency]);
}*/
});
render.js
'use strict';
onLoad();
function onLoad(){
/*document.querySelector('.one').addEventListener('click',() => {
writeLog()
});*/
/*var testButton = document.querySelector('button[class=test]');
testButton.addEventListener('click', (e) => {
//C:\\Program Files\\nvm\\v14.20.0\\node.exe
window.main.asyncProc( '"C:\\Program Files\\nvm\\v14.20.0\\node.exe" winax_microamp/index.js' );
});*/
var buttons = document.querySelectorAll('button');
buttons.forEach((button) => {
//console.log('button=');
//console.log(button.className);
button.addEventListener('click', (event) => {
window.request.send(button.className);
});
});
}
Ugh... ok, so a few things I figured out eventually, I took hours to figure this out, and I'm not 100% sure if I'm understanding it properly so I appreciate any correction.
I think one issue that threw me off for longer than it should have is that the console.log for the preload.js goes into the developer tools rather than the system console like the main thread areas do.
Another thing throwing me off was that I was writing the implementation inside of preload.js, rather than in render.js. I confirmed it does work in both places.
If I want it in preload.js, I leave the implementation like this, and I confirmed it was firing here based on changing the log message a little
preload.js
response: (message) => {
ipcRenderer.on('response', (message) => {
console.log('expose reply=');
console.log(message);
});
}
}
If I want it in render instead, I need my preload to be more basic
response: (message) => {
ipcRenderer.on('response', message);
}
And then I need render to have this to process it there instead.
window.request.response((event,message) => {
console.log('winax reply=');
console.log(message);
});

Codeceptjs on Google Cloud Function goto of undefined

I'm trying to automate a web activity using CodeceptJS (with Puppeteer) running in a Google Cloud Function.
My index.js is:
const Container = require('codeceptjs').container;
const Codecept = require('codeceptjs').codecept;
const event = require('codeceptjs').event;
const path = require('path');
module.exports.basicTest = async (req, res) => {
let message = '';
// helpers config
let config = {
tests: './*_test.js',
output: './output',
helpers: {
Puppeteer: {
url: 'https://github.com', // base url
show: true,
disableScreenshots: true, // don't store screenshots on failure
windowSize: '1200x1000', // set window size dimensions
waitForAction: 1000, // increase timeout for clicking
waitForNavigation: [ 'domcontentloaded', 'networkidle0' ], // wait for document to load
chrome: {
args: ['--no-sandbox'] // IMPORTANT! Browser can't be run without this!
}
}
},
include: {
I: './steps_file.js'
},
bootstrap: null,
mocha: {},
name: 'basic_test',
// Once a tests are finished - send back result via HTTP
teardown: (done) => {
res.send(`Finished\n${message}`);
}
};
// pass more verbose output
let opts = {
debug: true,
steps: true
};
// a simple reporter, let's collect all passed and failed tests
event.dispatcher.on(event.test.passed, (test) => {
message += `- Test "${test.title}" passed 😎`;
});
event.dispatcher.on(event.test.failed, (test) => {
message += `- Test "${test.title}" failed 😭`;
});
// create runner
let codecept = new Codecept(config, opts);
// codecept.init(testRoot)
codecept.initGlobals(__dirname);
// create helpers, support files, mocha
Container.create(config, opts);
try {
// initialize listeners
codecept.bootstrap();
// load tests
codecept.loadTests('*_test.js');
// run tests
codecept.run();
} catch (err) {
printError(err)
process.exitCode = 1
} finally {
await codecept.teardown()
}
}
and my simple test is:
Feature('Basic Automation');
Scenario('Basic Test', async ({ I }) => {
// ===== Login =====
pause()
I.amOnPage('https://cotps.com');
I.see('Built for developers', 'h1');
});
If I run it using npx codeceptjs run --steps it works but if I run it using node -e 'require("./index").basicTest() I get the error:
Cannot read property 'goto' of undefined. I also get the error if I deploy it to GCP and run it. I've looked through the docs for both Codecept and Puppeteer but found nothing and the only examples online are for previous versions of the libraries.

How to get the path to the directory of the opened file?

I'm Kenyon Bowers.
I have some code that opens a open file dialog. It opens .DSCProj (which are specific to my project), and I am going to run some terminal commands in the directory that the opened file is in.
I have no idea how to do that.
preload.ts:
import { ipcRenderer, contextBridge } from "electron";
import { dialog } from '#electron/remote'
contextBridge.exposeInMainWorld("api", {
showOpenFileDialog: () => dialog.showOpenDialogSync({
properties: ["openFile"],
filters: [
{
name: "DSC Projects",
extensions: ["DSCProj"],
},
],
})
});
NewProject.ts:
declare var api: any;
function OpenProject(): void {
const file = api.showOpenFileDialog();
console.log("Done")
if(file != null){
localStorage.setItem('DirPath', file);
location.href='./views/projectOpen.html'
}
}
(() => {
document.querySelector('#btn-open-project')?.addEventListener('click', () => {
OpenProject();
}),
document.querySelector('#btn-new-project')?.addEventListener('click', () => {
location.href='./views/projectNew.html'
})
})()
As you can see on line 7, I'm setting local storage to the file's path. But I need to set it to the path of the directory that the file is in.
Whilst use of #electron/remote is great and all, you may be better served by implementing certain Electron modules within the main thread instead of the render thread(s). This is primarily for security but as a strong second reason, it keeps your code separated. IE: Separation of concerns.
Unlike vanilla Javascript, node.js has a simple function path.parse(path).dir to easily remove the file name (and extension) from the file path without needing to worry about which OS (IE: Directory separator) you are using. This would also be implemented within your main thread. Implementing something like this within your render thread would take a lot more work with vanilla Javascript to be OS proof.
Lastly, in the code below I will use a preload.js script that only deals with the movement of messages and their data between the main thread and render thread(s). I do not believe that the concrete implementation of functions in your preload.js script(s) is the right approach (though others may argue).
Note: I am not using typescript in the below code, but you should get the general idea.
Let's use the channel name getPath within the invoke method.
preload.js (main thread)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': [
'getPath'
]
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
Now, in the main thread, let's listen for a call on the getPath channel. When called, open up the dialog and upon the return of a path, process it with Node's path.parse(path).dir function to remove the file name (and extension). Lastly, return the modified path.
main.js (main thread)
const electronBrowserWindow = require('electron').BrowserWindow;
const electronDialog = require('electron').dialog;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
let window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html')
.then(() => { window.show(); });
return window;
}
electronApp.on('ready', () => {
window = createWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// -----
// Let's listen for a call on the 'getPath' channel
electronIpcMain.handle('getPath', async () => {
// Dialog options.
const options = {
properties: ["openFile"],
filters: [
{
name: "DSC Projects",
extensions: ["DSCProj"],
}
]
}
// When available, return the modified path back to the render thread via IPC
return await openDialog(window, options)
.then((result) => {
// User cancelled the dialog
if (result.canceled === true) { return; }
// Modify and return the path
let path = result.filePaths[0];
let modifiedPath = nodePath.parse(path).dir; // Here's the magic.
console.log(modifiedPath); // Testing
return modifiedPath;
})
})
// Create an open dialog
function openDialog(parentWindow, options) {
return electronDialog.showOpenDialog(parentWindow, options)
.then((result) => { if (result) { return result; } })
.catch((error) => { console.error('Show open dialog error: ' + error); });
}
Here you will get the general idea about how to use the returned result.
index.html (render thread)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Open Project Test</title>
</head>
<body>
<input type="button" id="btn-open-project" value="Open Project">
</body>
<script>
document.getElementById('btn-open-project').addEventListener('click', () => {
openProject();
});
function openProject() {
window.ipcRender.invoke('getPath')
.then((path) => {
// As we are using "invoke" a response will be returned, even if undefined.
if (path === undefined) { return; } // When user cancels dialog.
console.log(path); // Testing.
// window.localStorage.setItem('DirPath', path);
// location.href='./views/projectOpen.html';
});
}
</script>
</html>

Dynamically mock dependencies with Jest

I have a method that logs a message via one function in a node environment and via a different function in a browser environment. To check whether I am in a node or browser environment I use the libraries detect-node and is-browser like so:
const isNode = require('detect-node');
const isBrowser = require('is-browser');
log(level, message, data) {
if (isNode) {
this.nodeTransport.log(level, this.name, message, data);
}
if (isBrowser) {
this.browserTransport.log(level, this.name, message, data);
}
}
The variables isNode and isBrowser are set to true and false (automatically via the package) depending on, well, if I'm in a browser or in a node env.
Now I want to test this behavior using jest so I need to mock these npm packages. This is what I tried:
function setup() {
const loggerName = 'Test Logger';
const logger = new Logger(loggerName);
logger.nodeTransport = { log: jest.fn() };
logger.browserTransport = { log: jest.fn() };
logger.splunkTransport = { log: jest.fn() };
return { logger, loggerName };
}
test('it should call the the appropriate transports in a node environment', () => {
const { logger } = setup();
const message = 'message';
jest.mock('detect-node', () => true);
jest.mock('is-browser', () => false);
logger.log('error', message, []);
expect(logger.nodeTransport.log).toHaveBeenCalled();
expect(logger.browserTransport.log).not.toHaveBeenCalled();
});
test('it should call the the appropriate transports in a browser environment', () => {
const { logger } = setup();
const message = 'message';
jest.mock('detect-node', () => false);
jest.mock('is-browser', () => true);
logger.log('error', message, []);
expect(logger.nodeTransport.log).not.toHaveBeenCalled();
expect(logger.browserTransport.log).toHaveBeenCalled();
});
You see, I am using jest.mock to mock detect-node and is-browser and give it different return values. However, this just does not work. The first test is green because (I assume) Jest runs in node, but the second test fails saying
Expected mock function not to be called but it was called with:
["error", "Test Logger", "message", []]
Use .mockClear() to reset the mock calls between tests.
afterEach(() => {
logger.nodeTransport.log.mockClear();
logger.browserTransport.log.mockClear();
});

running e2e testing with aurelia cli

I'm trying to implement a few e2e tests in my aurelia-cli app. I've tried looking for docs or blogs but haven't found anything on e2e setup for the cli. I've made the following adjustments to the project.
first I added this to aurelia.json
"e2eTestRunner": {
"id": "protractor",
"displayName": "Protractor",
"source": "test/e2e/src/**/*.ts",
"dist": "test/e2e/dist/",
"typingsSource": [
"typings/**/*.d.ts",
"custom_typings/**/*.d.ts"
]
},
Also added the e2e tasks on aurelia_project/tasks:
e2e.ts
import * as project from '../aurelia.json';
import * as gulp from 'gulp';
import * as del from 'del';
import * as typescript from 'gulp-typescript';
import * as tsConfig from '../../tsconfig.json';
import {CLIOptions} from 'aurelia-cli';
import { webdriver_update, protractor } from 'gulp-protractor';
function clean() {
return del(project.e2eTestRunner.dist + '*');
}
function build() {
var typescriptCompiler = typescriptCompiler || null;
if ( !typescriptCompiler ) {
delete tsConfig.compilerOptions.lib;
typescriptCompiler = typescript.createProject(Object.assign({}, tsConfig.compilerOptions, {
// Add any special overrides for the compiler here
module: 'commonjs'
}));
}
return gulp.src(project.e2eTestRunner.typingsSource.concat(project.e2eTestRunner.source))
.pipe(typescript(typescriptCompiler))
.pipe(gulp.dest(project.e2eTestRunner.dist));
}
// runs build-e2e task
// then runs end to end tasks
// using Protractor: http://angular.github.io/protractor/
function e2e() {
return gulp.src(project.e2eTestRunner.dist + '**/*.js')
.pipe(protractor({
configFile: 'protractor.conf.js',
args: ['--baseUrl', 'http://127.0.0.1:9000']
}))
.on('end', function() { process.exit(); })
.on('error', function(e) { throw e; });
}
export default gulp.series(
webdriver_update,
clean,
build,
e2e
);
and the e2e.json
{
"name": "e2e",
"description": "Runs all e2e tests and reports the results.",
"flags": []
}
I've added a protractor.conf file and aurelia.protractor to the root of my project
protractor.conf.js
exports.config = {
directConnect: true,
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome'
},
//seleniumAddress: 'http://0.0.0.0:4444',
specs: ['test/e2e/dist/*.js'],
plugins: [{
path: 'aurelia.protractor.js'
}],
// Options to be passed to Jasmine-node.
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000
}
};
aurelia.protractor.js
/* Aurelia Protractor Plugin */
function addValueBindLocator() {
by.addLocator('valueBind', function (bindingModel, opt_parentElement) {
var using = opt_parentElement || document;
var matches = using.querySelectorAll('*[value\\.bind="' + bindingModel +'"]');
var result;
if (matches.length === 0) {
result = null;
} else if (matches.length === 1) {
result = matches[0];
} else {
result = matches;
}
return result;
});
}
function loadAndWaitForAureliaPage(pageUrl) {
browser.get(pageUrl);
return browser.executeAsyncScript(
'var cb = arguments[arguments.length - 1];' +
'document.addEventListener("aurelia-composed", function (e) {' +
' cb("Aurelia App composed")' +
'}, false);'
).then(function(result){
console.log(result);
return result;
});
}
function waitForRouterComplete() {
return browser.executeAsyncScript(
'var cb = arguments[arguments.length - 1];' +
'document.querySelector("[aurelia-app]")' +
'.aurelia.subscribeOnce("router:navigation:complete", function() {' +
' cb(true)' +
'});'
).then(function(result){
return result;
});
}
/* Plugin hooks */
exports.setup = function(config) {
// Ignore the default Angular synchronization helpers
browser.ignoreSynchronization = true;
// add the aurelia specific valueBind locator
addValueBindLocator();
// attach a new way to browser.get a page and wait for Aurelia to complete loading
browser.loadAndWaitForAureliaPage = loadAndWaitForAureliaPage;
// wait for router navigations to complete
browser.waitForRouterComplete = waitForRouterComplete;
};
exports.teardown = function(config) {};
exports.postResults = function(config) {};
and I added a sample test in my test/e2e/src folder it doesn't get executed. I've also tried implementing a e2e test within the unit test folder since when I run au test I see that a chrome browser opens up.
describe('aurelia homepage', function() {
it('should load page', function() {
browser.get('http://www.aurelia.io');
expect(browser.getTitle()).toEqual('Home | Aurelia');
});
});
But this throws the error browser is undefined. Am I missing something with e2e testing with the cli? I know aurelia-protractor comes pre-installed but I don't see any way to run it.
I know this is a very late answer, but perhaps for others looking for an answer, you could try to import from the aurelia-protractor plugin
import {browser} from 'aurelia-protractor-plugin/protractor';

Resources