Open srmagura opened 1 year ago
As the side panel is not a part of the web content, playwright does not support it out of the box and we are not planning to work on it at the moment. Have a look at this page which explains current state of extension debugging in Playwright. You can also try to connect to the sidebar via raw CDP protocol.
Could you try setting the PW_CHROMIUM_ATTACH_TO_OTHER=1
environment variable? This might make it work which helped Microsoft Edge folks doing similar things.
Could you try setting the
PW_CHROMIUM_ATTACH_TO_OTHER=1
environment variable? This might make it work which helped Microsoft Edge folks doing similar things.
Hi @mxschmitt, could you provide some additional context on your suggestion? For example, I am not sure what JavaScript code to write in my test to attempt to open the Chrome side panel.
If you set this env var, it should/could be available via context.pages().
If you set this env var, it should/could be available via context.pages().
That is a good tip, but I don't think it will help in this case. The webpage rendered in the side panel does not "exist" until the side panel is opened by clicking on the extension icon.
I am going to look into @yury-s's suggestion of using raw CDP (though I know nothing about that protocol, so I am not sure if it will provide a solution).
@srmagura, did you manage to find a way to test the sidepanel? Are there any tips you can share?
Hi @keenua, I never got around to doing a deep dive into this issue, so I don't have any tips. Good luck finding a solution π
I figured out a way that works for me:
const button = new DOMParser().parseFromString(
'<button id="openSidePanel">Click to open side panel</button>',
'text/html'
).body.firstElementChild;
button.addEventListener('click', function () {
chrome.runtime.sendMessage({ type: 'open_side_panel' });
});
document.body.append(button);
service-worker.js
), add a listener for the open_side_panel
event.chrome.runtime.onMessage.addListener((message, sender) => {
// The callback for runtime.onMessage must return falsy if we're not sending a response
(async () => {
if (message.type === 'open_side_panel') {
// This will open a tab-specific side panel only on the current tab.
// @ts-ignore
await chrome.sidePanel.open({ tabId: sender.tab.id });
await chrome.sidePanel.setOptions({
tabId: sender.tab.id,
path: 'src/pages/sidepanel/sidepanel.html', // replace with your sidepanel index
enabled: true
});
}
})();
});
fixture.ts
file that loads an extension, as suggested in the docs. Also pass the PW_CHROMIUM_ATTACH_TO_OTHER
environment variable.import { test as base, chromium, type BrowserContext } from '@playwright/test';
import path from 'path';
process.env.PW_CHROMIUM_ATTACH_TO_OTHER = "1";
export const test = base.extend<{
context: BrowserContext;
extensionId: string;
}>({
context: async ({ }, use) => {
const pathToExtension = path.join(__dirname, 'your-extension');
const context = await chromium.launchPersistentContext('', {
headless: false,
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
});
await use(context);
await context.close();
},
extensionId: async ({ context }, use) => {
let [background] = context.serviceWorkers();
console.log("Background: ", background?.url() ?? "None");
if (!background)
background = await context.waitForEvent('serviceworker');
const extensionId = background.url().split('/')[2];
await use(extensionId);
},
});
export const expect = test.expect;
page.context().pages()
.test('has title', async ({ page }) => {
await page.goto('https://playwright.dev/');
await page.locator("#openSidePanel").click(); // this would open the side panel with your extension's content
const extension = page.context().pages()[3]; // accessing by index for simplicity, check the urls / extension id when actually using it
await extension.getByText("Login").click(); // this would be performed inside of the extension
});
One could probably replace the button with something less invasive, but it should be a good starting point for anyone trying to test their sidepanels.
@keenua Thanks for sharing your workaround, it seems to be working for us π.
TLDR:
process.env.PW_CHROMIUM_ATTACH_TO_OTHER = "1";
to your project// sidePanelPage is of Page type (same you commonly use when works with Playwright)
const sidePanelPage: Page = page.context().pages().find((value) => value.url().match(extensionId));
sidePanelPage.getByTestId('my-beauty-button').click()
It could be really useful if you implement this logic as a fixture so in that way you can get access to your sidePanelPage on any test you need.
Hi everyone, thanks to @keenua and to @agallardol for their workaround!
I would like to share some findings that I had to overcome, it may help someone else during their journey while testing chrome extensions using these workarounds:
First of all, I'm running all my tests using:
pnpm exec playwright test --ui
process.env.PW_CHROMIUM_ATTACH_TO_OTHER = "1"
, the viewport of the side panel itself has changed his dimensions, using the default viewport size, in order to change that I have changed the context args adding the --start-maximized
, so i will have the following:
//fixture.ts
import { type BrowserContext, chromium, test as base } from '@playwright/test';
import path from 'path';
process.env.PW_CHROMIUM_ATTACH_TO_OTHER = '1';
const pathToExtension = path.resolve(__dirname, '..', 'dist');
export const test = base.extend<{
context: BrowserContext;
extensionId: string;
}>({
// eslint-disable-next-line no-empty-pattern
context: async ({}, use) => {
const context = await chromium.launchPersistentContext('', {
locale: 'en-US',
headless: false,
args: [
--disable-extensions-except=${pathToExtension}
,
--load-extension=${pathToExtension}
,
// ! Needed for normalize the size of the side panel
'--start-maximized',
],
});
await use(context);
await context.close();
},
extensionId: async ({ context }, use) => {
let [background] = context.serviceWorkers();
if (!background) background = await context.waitForEvent('serviceworker');
const extensionId = background.url().split('/')[2];
await use(extensionId);
}, }); export const expect = test.expect;
Plus the viewport which has to be set a null on the devices configs
```ts
// playwright.config.ts
...
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
// Use prepared auth state.
storageState: 'playwright/.auth/user.json',
// ! Override setting from ...devices['Desktop Chrome'],
// ! Needed for work in conjunction with normalization the size of the side panel
deviceScaleFactor: undefined,
viewport: null
},
},
...
sidePanelPage
as described by @agallardol: the tests which was using it, were flaky somehow.
After some debugging I have found out that the page.context() .pages()
had in it two instances of the extension itself.
I guess it may happen because I'm going to attach my extensions to all urls and it may be attached to the about_blank at the begin of each test.
So I had to take always the last one in order to interact properly with the page itself: const sidePanelPage = page
.context()
.pages()
// * Two pages with the url inside, only the last one will be the one on the side panel
// * We want to find the last one, so we are reverting it
.reverse()
.find((value) => value.url().match(extensionId));
I would like to hear your thought about them, thanks a lot.
Hi, my company's product is a Chrome extension that renders UI inside the Chrome side panel, using the
chrome.sidePanel
API. I'm attaching a screenshot of our Chrome extension's UI, in case you are not familiar with the Chrome side panel.My feature request is: Can you make it so Playwright can open the Chrome side panel and interact with the webpage that is rendered inside it?
This would allow us (and other Chrome extension authors) to create fully end-to-end tests for our extensions using Playwright.
I have reviewed the Playwright docs and API, and could not find a way to open the Chrome side panel. It is not possible to open the side panel programmatically from the extension service worker, as far as I know. The user has to click the extension icon. (If the user already has the side panel open, e.g. to view their bookmarks, they can also get to our extension's page using the dropdown at the top of the side panel.)
Once the side panel is open, we would need a way to get access to its
Page
object from our Playwright test code.Workaround for the time being
Our current plan for testing the application is to have Playwright interact directly with the extension service worker. We can use Playwright's ability to execute JavaScript in the service worker's context to mimic the communication the service worker normally receives from the side panel.
This will give us test coverage of our service worker and content scripts, which is much better than nothing. That said, ideally we would like a true end-to-end test that also covers the UI we render in the side panel.
Thank you for this awesome library!!!
Screenshot of our application in the Chrome side panel: