microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
66.2k stars 3.62k forks source link

[BUG + FEATURED REQUEST] Take screenshot of a locator has height bigger than the viewport will left some place of the screenshot blank or capture the locator behind it. #27922

Closed Dinh246 closed 11 months ago

Dinh246 commented 11 months ago

System info

Source code

await expect(locator).toHaveScreenshot("expected.png")

Steps

Expected

Should fully capture screenshot of the locator

Actual

Playwright only capture the visible part in viewport, other parts which are hidden to the viewport will be blank. Also if we take screenshot in a popup (same condition locator's height > viewport's height) it will capture a part of the locator that in viewport and the behind part of the page.

Feature request I also figured solution for this by resizing the viewport if height, width of locator > viewport so I request to add an option that check the size of locator and resize the viewport before taking screenshot and change back when finish verify to avoid this bug.

pavelfeldman commented 11 months ago

Could you share a snippet that reproduces the issue so that I did not need to guess the right steps and locators?

Dinh246 commented 11 months ago
import { test, expect } from '@playwright/test';

test('test', async ({ page }) => {
  await page.goto('https://au-ins-quickview.myshopbase.net/collections/all');
  await page.locator("footer.footer-section").evaluate(ele =>
    ele.scrollIntoView({ behavior: "instant", block: "center" }),
  );
  await expect(page.locator("[class*=widget_setting_best_seller]")).toBeVisible();
  await page.locator("[class*=widget_setting_best_seller]").evaluate(ele =>
    ele.scrollIntoView({ behavior: "instant", block: "center" }),
  );
  await page.locator("[class*=widget_setting_best_seller]").locator("[class^=upsell-widget-product]").filter({ hasText: 'Product B @SB_SF_BUSF_QVA_19' }).hover();
  await page.getByRole('button', { name: 'Add to cart' }).first().click();
  await page.getByText('View Size Guide').click();
  await page.locator(".upsell-select select").selectOption({ label: 'V-neck T-shirt' })
  await expect(page.getByText('.qv-size-chart')).toHaveScreenshot();
});

I can share my solution if you interest too

pavelfeldman commented 11 months ago

Here is the error I'm getting when I run your script:

> 12 |   await page.locator("[class*=widget_setting_best_seller]").locator("[class^=upsell-widget-product]").hover();

Error: locator.hover: Error: strict mode violation: locator('[class*=widget_setting_best_seller]').locator('[class^=upsell-widget-product]') resolved to 6 elements:
    1) <div class="upsell-widget-product upsell-relative">…</div> aka locator('div').filter({ hasText: /^Target product @SB_SF_BUSF_QVA_18\$12\.99 USDAdd to cart$/ }).nth(1)
    2) <div class="upsell-widget-product upsell-relative">…</div> aka locator('div').filter({ hasText: /^Product cuối\$0\.00 USDAdd to cart$/ }).nth(1)
    3) <div class="upsell-widget-product upsell-relative">…</div> aka locator('div').filter({ hasText: /^Recommend product @SB_SF_BUSF_QVA_18\$255\.00 USDAdd to cart$/ }).nth(1)
    4) <div class="upsell-widget-product upsell-relative">…</div> aka locator('div').filter({ hasText: /^Product B @SB_SF_BUSF_QVA_19\$33\.99 USD\$48\.99 USDAdd to cart$/ }).nth(1)
    5) <div class="upsell-widget-product__original-price ups…>$48.99 USD</div> aka locator('#app_best-seller_1').getByText('$48.99 USD')
    6) <div class="upsell-widget-product upsell-relative">…</div> aka locator('div').filter({ hasText: /^Product 8 variants @SB_SF_BUSF_QVA_19\$502\.00 USDAdd to cart$/ }).nth(1)
Dinh246 commented 11 months ago

@pavelfeldman oops sorry I'm missing filter. Edited please copy and run again

pavelfeldman commented 11 months ago

~I can't even click add to cart due to the occlusion detection, we'll look into it.~, Ah, that's because you are clicking first() "Add to cart" and your product could be third. Should be

  const productLocator = page.locator('[class*=widget_setting_best_seller]').locator('[class^=upsell-widget-product]').filter({ hasText: 'Product B @SB_SF_BUSF_QVA_19' });
  await productLocator.hover();
  await productLocator.getByRole('button', { name: 'Add to cart' }).first().click();

but that is beside the topic of the issue.

pavelfeldman commented 11 months ago

The remainder of the issue seems to be due to the specificity of the page. We resize viewport momentarily and take a screenshot. And if you do that real quick, that is what we see on the screen. After some time the size chart would resize to take up all the space.

Dinh246 commented 11 months ago

@pavelfeldman I thought about it too and gave it a wait 10s but the result is the same. However, if I resize the viewport to bigger height than the locator I can capture the full screenshot as expected.

dgozman commented 11 months ago

@Dinh246 Unfortunately, waiting for any amount of time before the screenshot will not help. The issue is during the screenshot: since your target element is larger than the available viewport, browser temporarily resizes the page to be larger to accommodate the element. When page expects to perform some asynchronous changes upon resize, those changes are not captured on the screenshot.

The workaround would be to manually resize the page beforehand, such that the target element fits the viewport:

await page.setViewportSize({ width: 1280, height: 2000 });
await expect(page.getByText('.qv-size-chart')).toHaveScreenshot();

Let me know whether this helps.

dgozman commented 11 months ago

Closing because there is no action item for Playwright. If you still encounter problems, please file a new issue with a repro and link to this one.