How to disable contextIsolation and enable nodeIntegration for cypress electron tests? - electron

I am trying to access these properties (made available by this update).
I tried the following in cypress.config.ts but it does not seem to be working:
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
on("before:browser:launch", (browser, launchOptions) => {
launchOptions.preferences.webPreferences.contextIsolation = false;
launchOptions.preferences.webPreferences.nodeIntegration = true;
});
},
},
});
Thank you in advance.

Add a browser check as launch options are specific to Electron and will stop other browsers running. Then return the launch options.
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('before:browser:launch', (browser = {}, launchOptions) => {
if (browser.name === 'electron') {
launchOptions.preferences.webPreferences.nodeIntegration = true;
launchOptions.preferences.webPreferences.contextIsolation = false;
launchOptions.preferences.webPreferences.enableRemoteModule = true;
}
return launchOptions;
})
},
},
});

You must return the launchOptions object in your callaback for them to take effect.
https://docs.cypress.io/api/plugins/browser-launch-api#Modify-browser-launch-arguments-preferences-extensions-and-environment
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
on("before:browser:launch", (browser, launchOptions) => {
launchOptions.preferences.webPreferences.contextIsolation = false;
launchOptions.preferences.webPreferences.nodeIntegration = true;
return launchOptions;
});
},
},
});

Related

React Styleguidist: How to async set up webpackConfig when using MDXv2 with ES module-based remark/rehype plugins?

In order to use MDX v2 with remark and rehype plugins in react applications created with Create React App v5 I have to use CRACO (v7) and especially its ability to handle a configuration that is returned as a promise/async function. The reason is that CRACO uses CJS module-based configuration, while the remark and rehype plugins are ES modules. Now I want to use MDXv2 with remark/rehype plugins in React Styleguidist too.
This is my craco.config.js:
const { addAfterLoader, loaderByName } = require('#craco/craco')
const mdxplagues = require('./mdxplugins.js')
// https://github.com/facebook/create-react-app/pull/11886#issuecomment-1055054685
const ForkTsCheckerWebpackPlugin =
process.env.TSC_COMPILE_ON_ERROR === 'true'
? require('react-dev-utils/ForkTsCheckerWarningWebpackPlugin')
: require('react-dev-utils/ForkTsCheckerWebpackPlugin');
module.exports = async (env) => {
const mdxplagueConfig = await mdxplagues()
return {
webpack: {
configure: (webpackConfig) => {
addAfterLoader(webpackConfig, loaderByName('babel-loader'), {
test: /\.(md|mdx)$/,
loader: require.resolve('#mdx-js/loader'),
/** #type {import('#mdx-js/loader').Options} */
options: mdxplagueConfig,
})
// https://github.com/facebook/create-react-app/pull/11886#issuecomment-1055054685
webpackConfig.plugins.forEach((plugin) => {
if (plugin instanceof ForkTsCheckerWebpackPlugin) {
plugin.options.issue.exclude.push({ file: '**/src/**/?(*.){spec,test,cy}.*' });
}
})
return webpackConfig
}
}
}
}
And this is my mdxplugins.js that returns an async function returing the needed plugin configurations:
const textrTypoEnDashes = (input) => {
return input
.replace(/ -- /gim, '–')
}
module.exports = async () => {
const remarkGfm = (await import('remark-gfm')).default
const remarkImages = (await import('remark-images')).default
const remarkTextr = (await import('remark-textr')).default
const remarkAccessibleEmoji = (await import('#fec/remark-a11y-emoji')).default
const rehypeSlug = (await import('rehype-slug')).default
const textrTypoApos = (await import('typographic-apostrophes')).default
const textrTypoQuotes = (await import('typographic-quotes')).default
const textrTypoPossPluralsApos = (await import('typographic-apostrophes-for-possessive-plurals')).default
const textrTypoEllipses = (await import('typographic-ellipses')).default
//const textrTypoEmDashes = (await import('typographic-em-dashes')).default
const textrTypoNumberEnDashes = (await import('typographic-en-dashes')).default
return {
remarkPlugins: [
remarkGfm,
remarkImages,
remarkAccessibleEmoji,
[remarkTextr, {
plugins: [
textrTypoApos,
textrTypoQuotes,
textrTypoPossPluralsApos,
textrTypoEllipses,
// textrTypoEmDashes,
textrTypoNumberEnDashes,
textrTypoEnDashes,
],
options: {
locale: 'en-us'
}
}],
],
rehypePlugins: [
rehypeSlug,
],
}
}
My problem is now that I need to apply this webpack/MDXv2 plugin configuration to styleguide.config.js (v13.0.0). I tried...
module.exports = async () => {
return {
...
}
}
...but this doesn't seem to be supported. A look into React Styleguidist's sources, namely scripts/config.ts seems to confirm this.
How can I synchronously resolve my MDX plugin configuration that I return via an async function from my mdxplugin.js CJS module within Styleguidist's CJS configuration module?

How to pass geolocation permission to react-native-webview?

When using the geolcoation api in a react-native-webview, I am asked twice whether the app is allowed to use my current location. How is it possible to forward the already (not) granted permission to the webview?
I am currently using react-native 0.68 and react-native-webview 11.22.
First prompt:
Second prompt:
I should only be asked once for permission to use my current geolocation.
In case somebody faces the same problem, I solved this issue with the following workaround. I injected a custome javacscript into the webview to override the used geolocation api in the webview. My custome script does all the communication with the app. The app returns the geolocation and so the webview doesn't need to aks for permission.
Custome Script
export const getGeoLocationJS = () => {
const getCurrentPosition = `
navigator.geolocation.getCurrentPosition = (success, error, options) => {
window.ReactNativeWebView.postMessage(JSON.stringify({ event: 'getCurrentPosition', options: options }));
window.addEventListener('message', (e) => {
let eventData = {}
try {
eventData = JSON.parse(e.data);
} catch (e) {}
if (eventData.event === 'currentPosition') {
success(eventData.data);
} else if (eventData.event === 'currentPositionError') {
error(eventData.data);
}
});
};
true;
`;
const watchPosition = `
navigator.geolocation.watchPosition = (success, error, options) => {
window.ReactNativeWebView.postMessage(JSON.stringify({ event: 'watchPosition', options: options }));
window.addEventListener('message', (e) => {
let eventData = {}
try {
eventData = JSON.parse(e.data);
} catch (e) {}
if (eventData.event === 'watchPosition') {
success(eventData.data);
} else if (eventData.event === 'watchPositionError') {
error(eventData.data);
}
});
};
true;
`;
const clearWatch = `
navigator.geolocation.clearWatch = (watchID) => {
window.ReactNativeWebView.postMessage(JSON.stringify({ event: 'clearWatch', watchID: watchID }));
};
true;
`;
return `
(function() {
${getCurrentPosition}
${watchPosition}
${clearWatch}
})();
`;
};
Webview
import Geolocation from '#react-native-community/geolocation';
let webview = null;
<WebView
geolocationEnabled={ true }
injectedJavaScript={ getGeoLocationJS() }
javaScriptEnabled={ true }
onMessage={ event => {
let data = {}
try {
data = JSON.parse(event.nativeEvent.data);
} catch (e) {
console.log(e);
}
if (data?.event && data.event === 'getCurrentPosition') {
Geolocation.getCurrentPosition((position) => {
webview.postMessage(JSON.stringify({ event: 'currentPosition', data: position }));
}, (error) => {
webview.postMessage(JSON.stringify({ event: 'currentPositionError', data: error }));
}, data.options);
} else if (data?.event && data.event === 'watchPosition') {
Geolocation.watchPosition((position) => {
webview.postMessage(JSON.stringify({ event: 'watchPosition', data: position }));
}, (error) => {
webview.postMessage(JSON.stringify({ event: 'watchPositionError', data: error }));
}, data.options);
} else if (data?.event && data.event === 'clearWatch') {
Geolocation.clearWatch(data.watchID);
}
}}
ref={ ref => {
webview = ref;
if (onRef) {
onRef(webview)
}
}}
source={ url }
startInLoadingState={ true }
/>

How to close a child electron window?

childWin.close() doesn't work, because .close() can only work on the parent window.
I cannot use childWin.hide(), because I need to completely kill the child window and then create it again like it's created at initialization of the electron process. There doesn't seem to be any straightforward way of doing this.
'use strict'
import electron from 'electron'
import {
app,
protocol,
BrowserWindow
} from 'electron'
import {
createProtocol
} from 'vue-cli-plugin-electron-builder/lib'
import installExtension, {
VUEJS3_DEVTOOLS
} from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{
scheme: 'app',
privileges: {
secure: true,
standard: true
}
}])
let win
const winWidth = 1000
const winHeight = 700
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: winWidth,
height: winHeight,
webPreferences: {
// webSecurity false fixes CORS issues, remove in production!
webSecurity: false,
enableRemoteModule: true,
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: true,
contextIsolation: false
}
})
win.webContents.openDevTools({
mode: 'docked'
})
win.setMenuBarVisibility(false)
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
// Load the index.html when not in development
win.loadURL('app://./index.html')
}
}
let childWin
const childWidth = 60
const childHeight = 60
async function createChildWindow() {
// Create the browser window.
childWin = new BrowserWindow({
width: childWidth,
height: childHeight,
frame: false,
closeable: true,
webPreferences: {
// webSecurity false fixes CORS issues, remove in production!
webSecurity: false,
enableRemoteModule: true,
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: true,
contextIsolation: false
},
Parent: win
})
const modalPath = process.env.NODE_ENV === 'development' ?
`${process.env.WEBPACK_DEV_SERVER_URL}/#/electronMenu` :
`file://${__dirname}/index.html#electronMenu`
childWin.loadURL(modalPath)
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
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()
})
// 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', async() => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installExtension(VUEJS3_DEVTOOLS)
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
createWindow()
createChildWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
electron.ipcMain.on('updateTileDataInElectronMenu', (event, tiles) => {
childWin.close()
})

Disable Javascript in Playwright

Is it possible to define a browser with Javascript disabled to simulate how a crawler would view a page? There should be an option that can be passed.
You can pass javaScriptEnabled in the BrowserContext options:
const playwright = require("playwright");
(async () => {
const browser = await playwright.chromium.launch();
const context = await browser.newContext({
javaScriptEnabled: false
});
const page = await context.newPage();
// ...
await browser.close();
})();
In the case of #playwright/test (where you don't define browser.newContext yourself) you can set javaScriptEnabled in testOptions.
(1) In the playwright.config.js file:
const config = {
use: {
headless: false,
javaScriptEnabled: false
},
};
module.exports = config;
(2) or with test.use:
const { test, expect } = require('#playwright/test');
test.use({ javaScriptEnabled: false });
test('website without JavaScript', async ({ page }) => {
// ...
});

Video as a background image not working in Gatsby PWA on iOS

I created a opt-in app for potential interims for our company, i worked with Gatsby and for now am quite satisfied with the result. I made it an Progressive Web App as that is fairly easy with the gatsby plugin.
The PWA works great on Android and shows the background video as expected, but on iOS the video doesn't show.
I updated all the packages and dependencies to the last versions but that doesn't change a thing. I tried googling the issue but got a lot of search results off people trying to let a PWA play video in the background when the app is closed (not my case).
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `Afstuderen bij Arcady`,
short_name: `Afstuderen`,
start_url: `/`,
background_color: `#FFF`,
theme_color: `#00a667`,
display: `standalone`,
icon: `src/images/bear_green.png`,
},
},
'gatsby-plugin-offline',
And the content of the service worker
importScripts("workbox-v3.6.3/workbox-sw.js");
workbox.setConfig({modulePathPrefix: "workbox-v3.6.3"});
workbox.core.setCacheNameDetails({prefix: "gatsby-plugin-offline"});
workbox.skipWaiting();
workbox.clientsClaim();
/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
*/
self.__precacheManifest = [
{
"url": "webpack-runtime-aec2408fe3a97f1352af.js"
},
{
"url": "app-5b624d17337895ddf874.js"
},
{
"url": "component---node-modules-gatsby-plugin-offline-app-shell-js-b97c345e19bb442c644f.js"
},
{
"url": "offline-plugin-app-shell-fallback/index.html",
"revision": "ac0d57f6ce61fac4bfa64e7e08d076c2"
},
{
"url": "0-d2c3040ae352cda7b69f.js"
},
{
"url": "component---src-pages-404-js-cf647f7c3110eab2f912.js"
},
{
"url": "static/d/285/path---404-html-516-62a-0SUcWyAf8ecbYDsMhQkEfPzV8.json"
},
{
"url": "static/d/604/path---offline-plugin-app-shell-fallback-a-30-c5a-BawJvyh36KKFwbrWPg4a4aYuc8.json"
},
{
"url": "manifest.webmanifest",
"revision": "5a580d53785b72eace989a49ea1e24f7"
}
].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
workbox.routing.registerRoute(/(\.js$|\.css$|static\/)/, workbox.strategies.cacheFirst(), 'GET');
workbox.routing.registerRoute(/^https?:.*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css)$/, workbox.strategies.staleWhileRevalidate(), 'GET');
workbox.routing.registerRoute(/^https?:\/\/fonts\.googleapis\.com\/css/, workbox.strategies.staleWhileRevalidate(), 'GET');
/* global importScripts, workbox, idbKeyval */
importScripts(`idb-keyval-iife.min.js`)
const WHITELIST_KEY = `custom-navigation-whitelist`
const navigationRoute = new workbox.routing.NavigationRoute(({ event }) => {
const { pathname } = new URL(event.request.url)
return idbKeyval.get(WHITELIST_KEY).then((customWhitelist = []) => {
// Respond with the offline shell if we match the custom whitelist
if (customWhitelist.includes(pathname)) {
const offlineShell = `/offline-plugin-app-shell-fallback/index.html`
const cacheName = workbox.core.cacheNames.precache
return caches.match(offlineShell, { cacheName }).then(cachedResponse => {
if (cachedResponse) return cachedResponse
console.error(
`The offline shell (${offlineShell}) was not found ` +
`while attempting to serve a response for ${pathname}`
)
return fetch(offlineShell).then(response => {
if (response.ok) {
return caches.open(cacheName).then(cache =>
// Clone is needed because put() consumes the response body.
cache.put(offlineShell, response.clone()).then(() => response)
)
} else {
return fetch(event.request)
}
})
})
}
return fetch(event.request)
})
})
workbox.routing.registerRoute(navigationRoute)
let updatingWhitelist = null
function rawWhitelistPathnames(pathnames) {
if (updatingWhitelist !== null) {
// Prevent the whitelist from being updated twice at the same time
return updatingWhitelist.then(() => rawWhitelistPathnames(pathnames))
}
updatingWhitelist = idbKeyval
.get(WHITELIST_KEY)
.then((customWhitelist = []) => {
pathnames.forEach(pathname => {
if (!customWhitelist.includes(pathname)) customWhitelist.push(pathname)
})
return idbKeyval.set(WHITELIST_KEY, customWhitelist)
})
.then(() => {
updatingWhitelist = null
})
return updatingWhitelist
}
function rawResetWhitelist() {
if (updatingWhitelist !== null) {
return updatingWhitelist.then(() => rawResetWhitelist())
}
updatingWhitelist = idbKeyval.set(WHITELIST_KEY, []).then(() => {
updatingWhitelist = null
})
return updatingWhitelist
}
const messageApi = {
whitelistPathnames(event) {
let { pathnames } = event.data
pathnames = pathnames.map(({ pathname, includesPrefix }) => {
if (!includesPrefix) {
return `${pathname}`
} else {
return pathname
}
})
event.waitUntil(rawWhitelistPathnames(pathnames))
},
resetWhitelist(event) {
event.waitUntil(rawResetWhitelist())
},
}
self.addEventListener(`message`, event => {
const { gatsbyApi } = event.data
if (gatsbyApi) messageApi[gatsbyApi](event)
})
I expect the iOS PWA (safari) to show the video as it does on Android but instead it gives a grey screen.
I hope some one can help me out or point me in the right direction.
How big is your video ?
Last time I checked, iOS has a limit of 50MB for the cache of a PWA, so if your video is bigger than 50MB, that may be the reason it works only on Android (which doesn't have such restrictions).
I found this blog post that helped me fix this problem
To add Range request handling to gatsby-plugin-offline, I added a script called range-request-handler.js with the following:
// range-request-handler.js
// Define workbox globally
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.0.0/workbox-sw.js');
// Bring in workbox libs
const { registerRoute } = require('workbox-routing');
const { CacheFirst } = require('workbox-strategies');
const { RangeRequestsPlugin } = require('workbox-range-requests'); // The fix
// Add Range Request support to fetching videos from cache
registerRoute(
/(\.webm$|\.mp4$)/,
new CacheFirst({
plugins: [
new RangeRequestsPlugin(),
],
})
);
Then in my gatsby-config.js I configured the plugin to append the above script:
// gatsby-config.js
module.exports = {
// ...
plugins: [
// ...plugins
{
resolve: 'gatsby-plugin-offline',
options: {
appendScript: require.resolve('./range-request-handler.js'),
},
},
// ...plugins
],
// ...
};
Videos now work in the Safari browser for me. If there is a better way to implement this, I am all ears. For now it works as intended

Resources