When using Playwright, almost always use the Playwright assertion, which waits for the predicate to be true.
Consider the following simple example:
from playwright.sync_api import expect, sync_playwright
html = r"""<!DOCTYPE html><html><body>
<h1>NO!</h1>
<script>
setTimeout(() => {
document.querySelector("h1").textContent = "YES!"
}, 3000);
</script>
</body></html>
"""
def main():
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.set_content(html)
expect(page.locator("h1")).to_have_text("YES!")
browser.close()
if __name__ == "__main__":
main()
This passes, even though the site takes 3 seconds after load to change its header text from NO! to YES!. If you use any other assertion library, the auto-wait will not occur.
Now change the above code to fail: expect(page.locator("h1")).to_have_text("NEVER!"). The output will be clear:
AssertionError: Locator expected to have text 'NEVER!'
Actual value: YES!
Call log:
LocatorAssertions.to_have_text with timeout 5000ms
waiting for locator("h1")
locator resolved to <h1>NO!</h1>
unexpected value "NO!"
locator resolved to <h1>NO!</h1>
unexpected value "NO!"
locator resolved to <h1>NO!</h1>
unexpected value "NO!"
locator resolved to <h1>NO!</h1>
unexpected value "NO!"
locator resolved to <h1>NO!</h1>
unexpected value "NO!"
locator resolved to <h1>NO!</h1>
unexpected value "NO!"
locator resolved to <h1>NO!</h1>
unexpected value "NO!"
locator resolved to <h1>YES!</h1>
unexpected value "YES!"
locator resolved to <h1>YES!</h1>
unexpected value "YES!"
Notice it's retried and tracked changes over time, eventually emitting a clear error which makes debugging easier.
As an aside, there's no need to use f-strings if you're not concatenating anything: f"{self.live_server_url}" should be self.live_server_url.