Disable Javascript in Playwright - 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 }) => {
// ...
});

Related

Playwright Component Testing with ContextApi

I have created a small React app and I want to test it using Playwright component testing
I have 3 components: App -> ChildComponent -> ChildChildComponent
I want to render (mount) the ChildComponent directly, and make assertions on it, but when I do that, some ContextApi functions that are defined in the App in the normal flow, are now undefined as the App component is not part of the component test.
So i'v trying to render the ChildComponent together with a face ContextApi Provider and pass mocks of those undefined functions, and then I get an infinite render loop for some reason.
How can I go about this, as this use case is typical in react component test.
Here is the test with all my failed mocking attempts separated:
test.only("validate CharacterModal", async ({ page, mount }) => {
const data = ['some-mocked-irrelevant-data']
// const setCurrentCharacter = () => {};
// const setIsCharacterModalOpen = () => {};
// const setCurrentCharacterMocked = sinon.stub("setCurrentCharacter").callsFake(() => {});
// const setIsCharacterModalOpenMocked = sinon.stub("setCurrentCharacter").callsFake(() => {});
// const setCurrentCharacter = jest.fn();
// const setIsCharacterModalOpen = jest.fn();
// const setCurrentCharacter = (): void => {};
// const setIsCharacterModalOpen = (): void => {};
// const setIsCharacterModalOpen = (isCharacterModalOpen: boolean): void => {};
const AppContext = React.createContext<any>(null);
await page.route("**/users*", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(data),
});
});
const component = await mount(
<AppContext.Provider value={{ setCurrentCharacterMocked, setIsCharacterModalOpenMocked }}>
<CharacterModal />
</AppContext.Provider>
);
expect(await component.getByRole("img").count()).toEqual(4);
});
The beforeMount hook can be used for this. I recently added docs about this: https://github.com/microsoft/playwright/pull/20593/files.
// playwright/index.jsx
import { beforeMount, afterMount } from '#playwright/experimental-ct-react/hooks';
// NOTE: It's probably better to use a real context
const AppContext = React.createContext(null);
beforeMount(async ({ App, hooksConfig }) => {
if (hooksConfig?.overrides) {
return (
<AppContext.Provider value={hooksConfig.overrides}>
<App />
</AppContext.Provider>
);
}
});
// src/CharacterModal.test.jsx
import { test, expect } from '#playwright/experimental-ct-react';
import { CharacterModal } from './CharacterModal';
test('configure context through hooks config', async ({ page, mount }) => {
const component = await mount(<CharacterModal />, {
hooksConfig: { overrides: 'this is given to the context' },
});
});

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 send parallel POST requests in puppeteer?

I want to send parallel POST requests in puppeteer. I have to change the payload with every request (URL remains the same).
I tried using puppeteer cluster, but how do I change payload with every request when I queue the same request?
Using normal puppeteer
(async() => {
const browser = await puppeteer.launch({
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-web-security",
],
executablePath: 'C:/Program Files/..',
headless: false,
});
for(const id of Ids) {
const page = await browser.newPage();
await page.setDefaultNavigationTimeout(60000);
await page.evaluateOnNewDocument(() => {
// Some code
})
await page.setRequestInterception(true);
// Request intercept handler... will be triggered with
// each page.goto() statement
page.on('request', interceptedRequest => {
// Here, is where you change the request method and
// add your post data
var data = {
'method': 'POST',
'postData': JSON.stringify({
....
"similarMaterialId": `${id}`,
}),
'headers': {
.....
},
};
// Request modified... finish sending!
interceptedRequest.continue(data);
});
const response = await page.goto('https://.../getProductInfo');
const responseBody = await response.json();
try {
let title = responseBody.description;
let price = responseBody.price;
fs.appendFile('temp.tsv', `${title}\t${price}\n`, function (err) {
if (err) throw err;
})
}
catch {
console.log(id)
}
await page.close();
}
console.log("Code ended!!")
await browser.close();
})();
I want to create many pages in parallel on a single browser.

Using puppeteer, on TimeoutError screenshot the current state

I'm trying to screenshot a website using puppeteer, and on slow sites I receive a TimeoutError.
In this case, I'd like to get the screenshot of the current page state - is this possible? if so, how?
Code sample:
const puppeteer = require('puppeteer');
let url = "http://...";
let timeout = 30000;
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page
.goto(url, {waitUntil: 'load', timeout: timeout}).then(async () => {
await page
.screenshot({path: 'example.png'})
.catch(error => console.error(error));
})
.catch(error => {
if (error.name === "TimeoutError") {
// -----> calling await page.screenshot({path: 'example.png'}) gets stuck
} else {
console.error(error);
}
});
await browser.close();
})();
Don't use browser.close when using puppeteer in development, as this may cause the browser closed and puppeteer crashed.
const puppeteer = require('puppeteer')
let url = "https://www.tokopedia.com"
let filename = 'timeout.png'
let timeoutNum = 30000
;(async () => {
const browser = await puppeteer.launch({
headless: false
});
const [page] = await browser.pages ()
page.setViewport ({ width: 1366, height: 768 })
try {
await page.goto(url, {waitUntil: 'networkidle0', timeout: timeoutNum}).then(async () => {
await page.screenshot({ path: 'example.png', fullPage: true })
})
} catch (error) {
if (error.name === "TimeoutError") {
console.log (error.name)
console.log (`Screenshot saved as ${filename}`)
await page.screenshot({ path: filename, fullPage: true })
} else {
console.log (error)
}
}
})()

workbox.runtimeCaching.Handler in workbox 3?

I have implemented this class in workbox 2, now I have upgraded to version 3 but workbox.runtimeCaching.Handler is deprecated.
Can someone help me on how to develop it in workbox 3?*
importScripts('workbox-sw.prod.v2.1.2.js');
importScripts('workbox-runtime-caching.prod.v2.0.3.js');
importScripts('workbox-cache-expiration.prod.v2.0.3.js');
const workboxSW = new self.WorkboxSW();
class AlwaysNetworkWithCacheUpdateHandler extends workbox.runtimeCaching.Handler{
setCacheOptions(cacheOptions){
this.cacheOptions = cacheOptions;
}
handle({event}){
let requestWrapper = new workbox.runtimeCaching.RequestWrapper({
cacheName: this.cacheOptions.cacheName,
plugins:[
new workbox.cacheExpiration.CacheExpirationPlugin(this.cacheOptions.expirationOptions)
]
});
return (
requestWrapper
.fetchAndCache({
request: event.request,
waitOnCache: true
})
);
}
}
I'm not sure what you are going to reach, however, I used runtimeCaching for third-party requests (CDN), so now it is handling by regular way:
https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests
Strategies now do the job of RequestWrapper, choose one and use like so:
const strategy = workbox.strategies.networkFirst({
cacheName,
plugins: [
new workbox.expiration.Plugin({
maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24 *7,
})
],
});
const handler = async ({event}) => {
const request = new Request(event.request, {
mode: 'cors',
credentials: 'omit',
});
const cachedResponse = await caches.match(request, {
cacheName,
});
return cachedResponse || strategy.makeRequest({
event,
request,
});
}
router.registerRoute(
({ url }) => url.origin === 'http://example.com',
handler,
)
Example comes directly from this issue

Resources