discourse/spec/system/page_objects/cdp.rb
Alan Guo Xiang Tan 55b05c921b
DEV: Add client settled checks for system tests (#35230)
This change seeks to improve the reliability of our system tests by
resolving the lack of consistency in the state of the client side
application between steps in a system test. This is achieved by patching
various action methods in `Capybara::Playwright::Node` and
`Capybara::Playwright::Browser` so that the methods execute an async
JavaScript function on the client side that waits for the client side
application to reach a settled state. A settled state is currently
defined as:

1. No inflight ajax requests. (_messageBus and presence requests are
excluded_)
2. 2 event cycles of the Javascript event loop has happened for for all
"click", "input", "mousedown", "keydown", "focusin", "focusout",
"touchstart", "change", "resize", "scroll" DOM events that fired.

For debugging purposes, a `--debug-client-settled` CLI flag has been
added to `bin/rspec`. When used, detailed debugging information will be
printed to the browser's console as well as to `stdout` of the
`bin/rspec` process.

This change was inspired by
https://evilmartians.com/chronicles/flaky-tests-be-gone-long-lasting-relief-chronic-ci-retry-irritation
and the https://github.com/makandra/capybara-lockstep rubygem.
2025-10-10 11:03:18 +08:00

162 lines
4.4 KiB
Ruby

# frozen_string_literal: true
module PageObjects
class CDP
include Capybara::DSL
include SystemHelpers
include RSpec::Matchers
def allow_clipboard
page.driver.with_playwright_page do |pw_page|
pw_page.context.grant_permissions(["clipboard-read"], origin: pw_page.url)
pw_page.context.grant_permissions(["clipboard-write"], origin: pw_page.url)
end
end
def read_clipboard
page.evaluate_async_script("navigator.clipboard.readText().then(arguments[0])")
end
def write_clipboard(content, html: false)
if html
page.evaluate_async_script(
"navigator.clipboard.write([
new ClipboardItem({
'text/html': new Blob([arguments[0]], { type: 'text/html' }),
'text/plain': new Blob([arguments[0]], { type: 'text/plain' })
})
]).then(arguments[1])",
content,
)
else
page.evaluate_async_script(
"navigator.clipboard.writeText(arguments[0]).then(arguments[1])",
content,
)
end
end
def copy_test_image
image_path = "spec/fixtures/images/logo.png"
image_data = File.read(image_path)
image_base64 = Base64.strict_encode64(image_data)
page.evaluate_async_script(<<~JAVASCRIPT)
const htmlBlob = new Blob(['<img src="data:image/png;base64,placeholder"/>'], { type: 'text/html' });
const imageBlob = new Blob([Uint8Array.from(atob("#{image_base64}"), c => c.charCodeAt(0))], { type: 'image/png' });
const item = new ClipboardItem({ 'text/html': htmlBlob, 'image/png': imageBlob });
navigator.clipboard.write([item]).then(arguments[0]).catch(console.error);
JAVASCRIPT
end
def clipboard_has_text?(text, chomp: false, strict: true)
clipboard_text = chomp ? read_clipboard.chomp : read_clipboard
expect(clipboard_text).to strict ? eq(text) : include(text)
end
def copy_paste(text, html: false, css_selector: nil)
allow_clipboard
write_clipboard(text, html: html)
paste(css_selector:)
end
def paste(css_selector: nil)
if css_selector
find(css_selector).send_keys([PLATFORM_KEY_MODIFIER, "v"])
else
page.send_keys([PLATFORM_KEY_MODIFIER, "v"])
end
end
def with_network_disconnected
page.driver.with_playwright_page do |pw_page|
begin
cdp_client = pw_page.context.new_cdp_session(pw_page)
cdp_client.send_message(
"Network.emulateNetworkConditions",
params: {
offline: true,
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
},
)
yield
ensure
cdp_client.send_message(
"Network.emulateNetworkConditions",
params: {
offline: false,
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
},
)
end
end
end
def with_slow_download
page.driver.with_playwright_page do |pw_page|
begin
cdp_client = pw_page.context.new_cdp_session(pw_page)
cdp_client.send_message(
"Network.emulateNetworkConditions",
params: {
offline: false,
latency: 20_000,
downloadThroughput: 1,
uploadThroughput: -1,
},
)
yield
ensure
cdp_client.send_message(
"Network.emulateNetworkConditions",
params: {
offline: false,
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
},
)
end
end
end
def with_slow_upload
page.driver.with_playwright_page do |pw_page|
begin
cdp_client = pw_page.context.new_cdp_session(pw_page)
cdp_client.send_message(
"Network.emulateNetworkConditions",
params: {
offline: false,
latency: 20_000,
downloadThroughput: -1,
uploadThroughput: 1,
},
)
yield
ensure
cdp_client.send_message(
"Network.emulateNetworkConditions",
params: {
offline: false,
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
},
)
end
end
end
end
end