Playwright test with NX - node.js

I have an NX workspace with a single application called my-app. I would like to run Playwright tests for my-app application by using NX console. Currently NX doesn't support Playwright plugin, so I've created a custom NX executor according to this tutorial. I've created necessary files for executor. After, I registered custom e2e command in application's project.json file. The playwright configuration file stays in the my-app folder.
When I run nx run my-app:e2e, the executor is been executed, however for some reason, playwright doesn't start. Instead, I see an error.
When I run manually in the console the command triggered by nx run my-app:e2e which is npx playwright test --config=apps/my-app/playwright.config.ts the playwright starts and does necessary testing.
project.json
...
...
...
"e2e": {
"executor": "./tools/executors/playwright:playwright",
"options": {
"path": "apps/my-app/playwright.config.ts"
}
}
executor.json
{
"executors": {
"playwright": {
"implementation": "./impl",
"schema": "./schema.json",
"description": "Runs Playwright Test "
}
}
}
impl.ts
export default async function echoExecutor(
options: PlaywrightExecutorOptions,
context: ExecutorContext
) {
console.info(`Executing "Playwright"...`);
console.info(`Options: ${JSON.stringify(options, null, 2)}`);
const { stdout, stderr } = await promisify(exec)(
`npx playwright test --config=${options.path}`,
);
console.log(stdout);
console.error(stderr);
const success = !stderr;
return { success };
}
schema.json
{
"$schema": "http://json-schema.org/schema",
"type": "object",
"cli": "nx",
"properties": {
"path": {
"type": "string",
"description": "Path to the project"
}
}
}
package.json
{
"executors": "./executor.json"
}
I'm not sure but maybe the problem is in promisify? I'm trying to call npx with it. Maybe there is a different way to call npx in this context?
const { stdout, stderr } = await promisify(exec)(
`npx playwright test --config=${options.path}`,
);

I will recommend https://github.com/marksandspencer/nx-plugins/tree/main/packages/nx-playwright
You will need to install the plugin using
yarn add --dev #mands/nx-playwright
yarn playwright install --with-deps
Remove existing e2e app nx generate remove <APP-NAME>-e2e before generating a new one
Generate new e2e app
yarn nx generate #mands/nx-playwright:project <APP-NAME>-e2e --project <APP-NAME>
PS: I am also author of the plugin

I would suggest to use https://github.com/marksandspencer/nx-plugins/tree/main/packages/nx-playwright
It should work out of the box

Try to use pnpm nx ...
Firstly, in package.json define:
"scripts": {
"test": "playwright test --output build --workers 2",
"test:debug": "playwright test --output build --debug",
"test:headed": "playwright test --output build --headed",
"test:codegen": "playwright codegen https://xxx.xxx.com/ -o records.test.ts",
"test:codegen-json": "playwright codegen https://xxx.xxx.com/ --save-storage=storage/auth_1.json",
"test:api": "playwright test src/e2e-api/ --retries 0 --output build "
}
Then, in common line:
pnpm nx test e2e-tests --skip-nx-cache
or
pnpm nx test:debug e2e-tests --skip-nx-cache
or
pnpm nx test:headed e2e-tests --skip-nx-cache
e2e-tests is a e2e destination folder where the package.json is located
--skip-nx-cache - to skip nx cache output
This command line you can use in any place, for example in gitlab-ci.yaml

Related

run cypress using tags

I am using Cypress (version:10+) + Cucumber+ Typescript. I need to run the test using tags. Also, I tried cypress-tag but it's not working. Is there a way I can run the cypress test using tags without skipping the test?
You can refer to this sample repository for your setup check it here:
https://github.com/badeball/cypress-cucumber-preprocessor/tree/master/examples/browserify-ts
in your cypress.config.ts
import { defineConfig } from "cypress";
import { addCucumberPreprocessorPlugin } from "#badeball/cypress-cucumber-preprocessor";
import browserify from "#badeball/cypress-cucumber-preprocessor/browserify";
async function setupNodeEvents(
on: Cypress.PluginEvents,
config: Cypress.PluginConfigOptions
): Promise<Cypress.PluginConfigOptions> {
await addCucumberPreprocessorPlugin(on, config);
on(
"file:preprocessor",
browserify(config, {
typescript: require.resolve("typescript"),
})
);
// Make sure to return the config object as it might have been modified by the plugin.
return config;
}
export default defineConfig({
e2e: {
specPattern: "**/*.feature",
supportFile: false,
setupNodeEvents,
},
});
in your package.json should contain the following dependencies and important to set cypress-cucumber-preprocessor settings "filterSpecs: true" and "omitFiltered: true" to run successfully through tags
{
"dependencies": {
"#badeball/cypress-cucumber-preprocessor": "latest",
"#cypress/browserify-preprocessor": "latest",
"cypress": "latest",
"typescript": "latest"
},
"cypress-cucumber-preprocessor": {
"filterSpecs": true,
"omitFiltered": true
}
}
then you can run your feature files like this:
cypress run --env tags=#foo
Best solution to it is the Cucumber Cypress preprocessor. I was able run my test using tags without any issue. The problem I faced in Cypress version 10 was the Itegration folder in the Cypress folder structure was renamed to e2e folder. And in Cucumber-Cypress-preprocessor will always look for files in integration folder (which was there in Cypress version less than 10) for searching tags.
I think the better solution is cypress-grep you can check about cypress-grep in the follow link https://github.com/cypress-io/cypress-grep

How to configure Vite to output single bundles for a Chrome DevTools Extension?

I am trying to create a Chrome DevTools Extension with Vite.
There are a couple different entry points. The main two are src/background.ts and devtools.html (which references src/devtools.ts).
There are is some code that I want to shared between them in src/devtools-shared.ts.
After running the build, the entry points still contain import statements. Why and how do I get rid of them so they are self-contained bundles (Ideally not IIFE, just good old top level scripts)?
Here is what I have got:
vite.config.js:
const { resolve } = require('path')
const { defineConfig } = require('vite')
module.exports = defineConfig({
resolve: {
alias: {
"root": resolve(__dirname),
"#": resolve(__dirname, "src")
}
},
esbuild: {
keepNames: true
},
build: {
rollupOptions: {
input: {
'background': "src/background.ts",
'content-script': "src/content-script.ts",
'devtools': "devtools.html",
'panel': "panel.html",
},
output: {
entryFileNames: chunkInfo => {
return `${chunkInfo.name}.js`
}
},
// No tree-shaking otherwise it removes functions from Content Scripts.
treeshake: false
},
// TODO: How do we configured ESBuild to keep functions?
minify: false
}
})
src/devtools-shared.ts:
export const name = 'devtools'
export interface Message {
tabId: number
}
src/background.ts:
import * as DevTools from './devtools-shared'
src/devtools.ts:
import * as DevTools from './devtools-shared'
And then in dist/background.js I still have:
import { n as name } from "./assets/devtools-shared.8a602051.js";
I have no idea what controls this. I thought there would not be any import statements.
Does the background.ts entry point need to be a lib or something?
For devtools.html is there some other option that controls this?
I know there is https://github.com/StarkShang/vite-plugin-chrome-extension but this doesn't work very well with Chrome DevTool Extensions. I prefer to configure Vite myself.
It turns out that this is not possible. Rollup enforces code-splitting when there are multiple entry-points. See https://github.com/rollup/rollup/issues/2756.
The only workaround that I can think of is to have multiple vite.config.js files such as:
vite.config.background.js
vite.config.content-script.js
vite.config.devtools.js
Then do something like this in package.json:
"scripts": {
"build": "npm-run-all clean build-background build-content-script build-devtools",
"build-background": "vite build -c vite.config.background.js",
"build-content-script": "vite build -c vite.config.content-script.js",
"build-devtools": "vite build -c vite.config.devtools.js",
"clean": "rm -rf dist"
},
This is not very efficient as it repeats a lot of work between each build but that's a Rollup problem.

Run another yarn/npm task within a package.json, without specifying yarn or npm

I have a task in my package.json "deploy", which needs to first call "build". I have specified it like this:
"deploy": "yarn run build; ./deploy.sh",
The problem is that this hard codes yarn as the package manager. So if someone doesn't use yarn, it doesn't work. Switching to npm causes a similar issue.
What's a good way to achieve this while remaining agnostic to the choice of npm or yarn?
One simple approach is to use the npm-run-all package, whose documentation states:
Yarn Compatibility
If a script is invoked with Yarn, npm-run-all will correctly use Yarn to execute the plan's child scripts.
So you can do this:
"predeploy": "run-s build",
"deploy": "./deploy.sh",
And the predeploy step will use either npm or yarn depending on how you invoked the deploy task.
I think it is good to have the runs in package.json remain package manager agnostic so that they aren't tied to a specific package manager, but within a project, it is probably prudent to agree on the use of a single package manager so that you're not dealing with conflicting lockfiles.
It's probably not ideal, but you could run a .js file at your project root to make these checks...
You could create a file at your project root called yarnpm.js (or whatever), and call said file in your package.json deploy command..
// package.json (trimmed)
"scripts": {
"deploy": "node yarnpm",
"build": "whatever build command you use"
},
// yarnpm.js
const fs = require('fs');
const FILE_NAME = process.argv[1].replace(/^.*[\\\/]/, '');
// Command you wish to run with `{{}}` in place of `npm` or `yarn'
// This would allow you to easily run multiple `npm`/`yarn` commands without much work
// For example, `{{}} run one && {{}} run two
const COMMAND_TO_RUN = '{{}} run build; ./deploy.sh';
try {
if (fs.existsSync('./package-lock.json')) { // Check for `npm`
execute(COMMAND_TO_RUN.replace('{{}}', 'npm'));
} else if (fs.existsSync('./yarn.lock')) { // Check for `yarn`
execute(COMMAND_TO_RUN.replace('{{}}', 'yarn'));
} else {
console.log('\x1b[33m', `[${FILE_NAME}] Unable to locate either npm or yarn!`, '\033[0m');
}
} catch (err) {
console.log('\x1b[31m', `[${FILE_NAME}] Unable to deploy!`, '\033[0m');
}
function execute(command) { // Helper function, to make running `exec` easier
require('child_process').exec(command,
(error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(stdout);
});
}
Hope this helps in some way! Cheers.
EDIT:
...or if you wanted to parameterize the yarnpm.js script, to make it easily reusable, and to keep all "commands" inside the package.json file, you could do something like this..
// package.json (trimmed, parameterized)
"scripts": {
"deploy": "node yarnpm '{{}} run build; ./deploy.sh'",
"build": "node build.js"
},
// yarnpm.js (parameterized)
const COMMAND_TO_RUN = process.argv[2]; // Technically, the first 'parameter' is the third index
const FILE_NAME = process.argv[1].replace(/^.*[\\\/]/, '');
if (COMMAND_TO_RUN) {
const fs = require('fs');
try {
if (fs.existsSync('./package-lock.json')) { // Check for `npm`
execute(COMMAND_TO_RUN.replace('{{}}', 'npm'));
} else if (fs.existsSync('./yarn.lock')) { // Check for `yarn`
execute(COMMAND_TO_RUN.replace('{{}}', 'yarn'));
} else {
console.log('\x1b[33m', `[${FILE_NAME}] Unable to locate either npm or yarn!`, '\033[0m');
}
} catch (err) {
console.log('\x1b[31m', `[${FILE_NAME}] Unable to deploy!`, '\033[0m');
}
function execute(command) { // Helper function, to make running `exec` easier
require('child_process').exec(command,
(error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(stdout);
});
}
} else {
console.log('\x1b[31m', `[${FILE_NAME}] Requires a single argument!`, '\033[0m')
}
What if check before run?
You can create a new file called build.sh, and it's content below:
# check if current user installed node environment, if not, auto install it.
if command -v node >/dev/null 2>&1; then
echo "version of node: $(node -v)"
echo "version of npm: $(npm -v)"
else
# auto install node environment, suppose platform is centos,
# need change this part to apply other platform.
curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash -
yum -y install nodejs
fi
npm run build
Then your script will be:
{
"deploy": "./build.sh && ./deploy.sh"
}
So I think I have a much simpler solution:
"deploy": "yarn run build || npm run build; ./deploy.sh",
Its only real downside is in the case where yarn exists, but the build fails, then npm run build will also take place.

Load .env environment variables when running npm task

Let's say we have a .env file with some variables specified:
AWS_PROFILE=hsz
ENVIRONMENT=development
There is also a simple npm task defined:
{
"name": "project",
"version": "0.0.1",
"scripts": {
"deploy": "sls deploy"
}
}
But runnning npm run deploy ignores our .env definition.
It can be resolved with better-npm-run like:
{
"name": "project",
"version": "0.0.2",
"scripts": {
"deploy": "bnr deploy"
},
"betterScripts": {
"deploy": "sls deploy"
},
"devDependencies": {
"better-npm-run": "^0.1.1",
}
}
but this looks like an overhead - especially when we have 10+ tasks.
Is there a better way to always load .env without proxying all tasks via better-npm-run?
A bit ugly, but you could try something like this:
"scripts": {
"deploy": "export $(cat .env | xargs) && sls deploy"
}
This will export all environment variables from the .env file before running sls deploy.
There are some variations to this tehnique in this answer.
Not very clean but it avoids usage of an extra module.
You can use env-cmd npm package to set environment variables loaded from .env file before executing a npm script.
Add package to your package.json devDependencies:
npm i env-cmd -D
Prefix your npm script with env-cmd program in package.json:
{
"scripts": {
"deploy": "env-cmd sls deploy"
}
}
Maintain and load all your environment specific configuration in project itself.
dev.js
module.exports = {
"host":"dev.com"
}
prod.js
module.exports = {
"host":"prod.com"
}
config.js - main file that will resolve configuration based on process.env.ENV variable.
const dev = require('./dev');
const prod = require('./prod');
let envObject = {};
const env = process.env.ENV || "dev";
switch(env) {
case 'prod':
envObject = prod;
break;
default:
envObject = dev;
}
envObject['ENV'] = env;
process.env = Object.assign(process.env,envObject); // Optional if you prefer to add them into process environment otherwise `require('./config')` where you need configuration.
module.exports = envObject;
index.js - node project root file call every time when project start
const config = require('./config');
console.log('config object => ',config.host);
package.json
{
"name": "project",
"version": "0.0.2",
"scripts": {
"deploy": "sls deploy"
}
}
Running you node.js code
Prod environment ENV=prod npm run deploy;
Development environment - npm run deploy;
Default environment is set to dev in ./config.js
Using this simple practice you don't need any npm module to manage your environment configurations.
I was having the same issue while trying to syncing the DB using an external command and fixed the issue by requiring dotenv package which will load the variables
"scripts": {
"db-sync": "node --require dotenv/config ./src/sequelize/sync.js"}
then just call npm run db-sync

Packaging Keytar with an Electron app

I'm using electron-builder (16.6.2) to package my electron application which includes keytar (3.0.2) as a prod dependency.
package.json file includes:
"scripts": {
"postinstall": "install-app-deps",
"compile:dev": "webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js",
"compile": "webpack --config webpack.build.config.js",
"dist": "yarn compile && build"
},
"build": {
"appId": "com.myproject",
"asar": true,
"files": [
"bin",
"node_modules",
"main.js"
]
}
When I run the .app on the same system it runs fine. When I try running it on a different system (or deleting my node_modules) it fails to find keytar.node. When keytar is built, it includes a fully qualified path to that image for my system. I get the following error in the console:
Uncaught Error: Cannot open /Users/Kevin/Work/myproject/node_modules/keytar/build/Release/keytar.node
Error: dlopen(/Users/Kevin/Work/myproject/node_modules/keytar/build/Release/keytar.node,
1): image not found
I must be missing a step in the build process.
As it turns out, I was using keytar in the renderer process. I moved keytar into the main process (which doesn't go through Webpack / Babel) and gets packed correctly by electron-builder.
main.js
ipcMain.on('get-password', (event, user) => {
event.returnValue = keytar.getPassword('ServiceName', user);
});
ipcMain.on('set-password', (event, user, pass) => {
event.returnValue = keytar.replacePassword('ServiceName', user, pass);
});
Then from the renderer process I can call
const password = ipcRenderer.sendSync('get-password', user);
or
ipcRenderer.sendSync('set-password', user, pass);
window.require("electron").remote.require("keytar")
Since you are working on renderer process and want to use native api from system or main process.
Update:
I found (as per the OP) that transpiling my main thread code (which uses keytar) resulted in calls to keytar functions returning TypeError: keytar.findPassword is not a function.
I had to use webpack-asset-relocator-loader to bundle keytar successfully:
npm i -DE #vercel/webpack-asset-relocator-loader
Add the following rule to your webpack.config.js:
module: {
rules: [{
test: /\.node$/,
parser: { amd: false },
use: {
loader: "#vercel/webpack-asset-relocator-loader",
options: {
outputAssetBase: "native_modules"
}
}
},
// <other rules>
],
// rest of config
}
Solution found in this Github issue.
The information below still stands for including binary assets in your webpack build.
If you have to transpile code that requires a binary file, you can add file-loader to your webpack config.
Install
npm i -D file-loader
or
yarn add -D file-loader
webpack config (to include a .dat file)
...,
module: {
rules: [{
...
}, {
test: /\.dat$/,
use: {
loader: "file-loader"
}
}]
},
...
If you want to preserve the filename, you can pass name options to the loader:
use: {
loader: "file-loader",
options: {
name: "[name].[ext]"
}
}
More information on the file-loader Github repo.

Resources