[Feature]: Extend isVisible/toBeVisible API to include overlap detection

Đã đóng
#43,849 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

  • #43861 của @microsoft-playwright-automation — đã đóng, không merge

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
15/100
Loại issue
Tính năng
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
testing-qa, typescript
Lĩnh vực
api, testing

Hướng nghiên cứu

This is an API design question, so start by deciding whether the flag goes on isVisible/toBeVisible or a new isNotOverlapped/toBeNotOverlapped method, and get maintainer agreement first. The issue's snippet uses document.elementFromPoint on sample points inside the element, with pointer-events temporarily set to all, and the example expects toBeVisible({checkOverlaps: true}) to fail when #big-box covers #small-box. Done means a settled API plus a test covering that case and the default behaviour staying unchanged.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

🚀 Feature Request

Extend the isVisible and toBeVisible calls to have an optional flag checkOverlaps (or enforceNotOverlapped if we want to be more precise), which would check if the element is overlapped by another one and thus not visible. For backward-compatibility, the flag would have a default value of false, in which case isVisible/toBeVisible would work exactly the same way as it does now.

If there is a strong argument for keeping these existing APIs unchanged, a new method could also be introduced (e.g. isNotOverlapped/toBeNotOverlapped).

Here's a possible implementation (does not consider transformations yet) that we use in our codebase to check overlaps.


function isElementNotOverlapped (element: Locator): Promise<boolean> {
  return element.evaluate((el) => {
    // pointer-events: none interferes with elementFromPoint so we temporarily set the property to all
    const originalPointerEvents = el.style.pointerEvents

    el.style.pointerEvents = 'all'
    const rect = el.getBoundingClientRect()

    const getStyleValueAsNumber = (styleProperty: string) => {
      return Number(window.getComputedStyle(el, null).getPropertyValue(styleProperty)
        .replace('px', ''))
    }

    const paddingLeft = getStyleValueAsNumber('padding-left')
    const paddingRight = getStyleValueAsNumber('padding-right')
    const paddingTop = getStyleValueAsNumber('padding-top')
    const paddingBottom = getStyleValueAsNumber('padding-bottom')
    const borderRadiusTopLeft = getStyleValueAsNumber('border-top-left-radius')
    const borderRadiusTopRight = getStyleValueAsNumber('border-top-right-radius')
    const borderRadiusBottomLeft = getStyleValueAsNumber('border-bottom-left-radius')
    const borderRadiusBottomRight = getStyleValueAsNumber('border-bottom-right-radius')

    const isPointVisible = (x: number, y: number) => {
      const elementAtPoint = document.elementFromPoint(x, y)

      return el.contains(elementAtPoint) || elementAtPoint === el
    }

    const pointsToCheckOffset = 2
    const leftEdgeToCheck = rect.left + paddingLeft + pointsToCheckOffset
    const topEdgeToCheck = rect.top + paddingTop + pointsToCheckOffset
    const rightEdgeToCheck = rect.right - paddingRight - pointsToCheckOffset
    const bottomEdgeToCheck = rect.bottom - paddingBottom - pointsToCheckOffset

    const pointsToCheck = [
      // top-left corner
      {x: leftEdgeToCheck + (borderRadiusTopLeft / 3), y: topEdgeToCheck + (borderRadiusTopLeft / 3)},
      // top-right corner
      {x: rightEdgeToCheck - (borderRadiusTopRight / 3), y: topEdgeToCheck + (borderRadiusTopRight / 3)},
      // bottom-left corner
      {x: leftEdgeToCheck + (borderRadiusBottomLeft / 3), y: bottomEdgeToCheck - (borderRadiusBottomLeft / 3)},
      // bottom-right corner
      {x: rightEdgeToCheck - (borderRadiusBottomRight / 3), y: bottomEdgeToCheck - (borderRadiusBottomRight / 3)},
      // center
      {x: rect.left + (rect.width / 2), y: rect.top + (rect.height / 2)}
    ]

    const result = pointsToCheck.every((point) => isPointVisible(point.x, point.y))

    el.style.pointerEvents = originalPointerEvents

    return result
  })
Example
await page.goto('data:text/html,<html><body style="margin: 0;">' +
    '<div id="small-box" style="background-color: green; height: 50px; width: 50px; position: fixed; top: 80px">' +
    '</div><div id="big-box" style="background-color: brown; height: 100px; width: 100px; position: fixed;">' +
    '</div></html>')

const smallBox = page.locator('#small-box')

// Passes
await expect(smallBox).toBeVisible()
// Passes
await expect(smallBox).toBeInViewport()
  
// Does not pass, since overlapped by `#big-box`.
await expect(smallBox).toBeVisible({checkOverlaps: true})
Motivation

As described in this previous issue (as well as this one), isVisible currently does not consider overlaps at all, so if an element is fully behind another one, isVisible may still return true for it. It would be useful to be able to test that an element is actually visible on the screen, since some visual bugs can offer due to overlapping elements and these are currently hard to write tests for using Playwright. Modern web apps often feature a lot of popups, modals etc. that may overlap other content on the screen.

Previously tracked in https://github.com/microsoft/playwright/issues/34778.

Ngôn ngữ chính
TypeScript
Star
99.1k
Fork
6.8k
Merge trung bình
17 giờ 6 phút
Pull request đã merge (30 ngày)
306

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của microsoft/playwright

Tất cả issue của microsoft/playwright

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.