[{"data":1,"prerenderedAt":888},["ShallowReactive",2],{"SiteFooter_cLEacilcm59lTjFJjLY6mXNWLL0ETtzOIiZwIHgDpy0":3,"why-tools":17,"nav-compare":869,"editor-sn7ff1":878,"editor-1vj0wnu":880,"editor-7qsdtz":882,"editor-h89y8c":884,"editor-dikysr":886},["Island",4],{"key":5,"result":6},"SiteFooter_cLEacilcm59lTjFJjLY6mXNWLL0ETtzOIiZwIHgDpy0",{"head":7},{"style":8},[9,11,13,15],{"innerHTML":10},".site-footer[data-v-8bb1d4ae]{border-top:1px solid var(--line);background:var(--chrome);padding:var(--sp-8) var(--gutter) var(--sp-10);gap:var(--sp-6);flex-direction:column;display:flex}.links[data-v-8bb1d4ae]{gap:var(--sp-4);grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));max-width:62rem;display:grid}.links a[data-v-8bb1d4ae]{align-items:flex-start;gap:var(--sp-3);padding:var(--sp-3);border-bottom:1px solid var(--line);border-radius:var(--radius-sm);background:var(--panel);color:var(--ink);border:1px solid var(--line);display:flex}.links a[data-v-8bb1d4ae]:focus-visible,.links a[data-v-8bb1d4ae]:hover{border-color:var(--line-strong)}.links[data-v-8bb1d4ae] .mark,.links[data-v-8bb1d4ae] .rf-mark{width:1.35rem;height:1.35rem;color:var(--dim);flex:none;margin-top:.1rem}.links a[data-v-8bb1d4ae]:hover .mark,.links a[data-v-8bb1d4ae]:hover .rf-mark{color:var(--red)}.links span[data-v-8bb1d4ae]{flex-direction:column;gap:2px;min-width:0;display:flex}.links b[data-v-8bb1d4ae]{font-family:var(--font-display);font-size:.85rem;font-weight:400}.links small[data-v-8bb1d4ae]{color:var(--faint);font-size:.78rem}.colophon[data-v-8bb1d4ae]{color:var(--faint);margin:0;font-size:.75rem}@supports (corner-shape:bevel){.links a[data-v-8bb1d4ae]{corner-shape:bevel}}",{"innerHTML":12},".mark[data-v-cc0d96a5]{vertical-align:-.125em;flex:none;width:1em;height:1em}",{"innerHTML":14},".mark[data-v-26f5673d]{vertical-align:-.125em;flex:none;width:1em;height:1em}",{"innerHTML":16},".rf-mark[data-v-26317b3e]{fill:currentColor;stroke:none;flex:none}",[18,414,621],{"id":19,"title":20,"body":21,"comparedAgainst":404,"description":27,"extension":405,"meta":406,"navigation":106,"order":73,"path":407,"seo":408,"slug":409,"stem":410,"tagline":411,"tool":412,"__hash__":413},"why\u002Fwhy\u002Fcypress.md","Browser vs Cypress",{"type":22,"value":23,"toc":397},"minimark",[24,28,36,41,44,52,60,161,164,169,175,179,182,189,192,195,218,251,254,261,265,268,282,296,302,312,315,319,322,328,347,350,354,357,374,377,393],[25,26,27],"p",{},"Cypress made end-to-end testing pleasant when it was not, and a lot of people\nlearned that tests can be fast and debuggable because Cypress showed them. Credit\nwhere it is due.",[25,29,30,31,35],{},"The argument below is not that Cypress is bad. It is that Cypress bought its\ndeveloper experience by moving the test ",[32,33,34],"em",{},"into"," the browser, and most of what is\nawkward about it follows from that one decision.",[37,38,40],"h2",{"id":39},"_1-we-are-built-on-playwright-so-there-is-nothing-to-work-around","1. We are built on Playwright, so there is nothing to work around",[25,42,43],{},"Cypress runs your test code inside the browser, in the same event loop as the\napplication under test. That is where the live-reloading runner comes from. It\nis also where the constraints come from, and you tend to meet those later.",[25,45,46,47,51],{},"Browser drives the browser from outside, over Playwright. There is no\nsame-origin boundary to work around, no ",[48,49,50],"code",{},"cy.origin()"," wrapper for visiting a\nsecond domain, no special case for a login flow that redirects through an\nidentity provider. Multiple tabs are multiple pages. Multiple browsers in one\ntest are multiple browsers.",[25,53,54,55,59],{},"The case that decides it for a lot of applications is ",[56,57,58],"strong",{},"two users at once",".\nChat, a shared document, a seller and a bidder, an approval queue, a support\nagent and the customer they are helping — one test, two logged-in sessions,\neach seeing what the other does:",[61,62,67],"pre",{"className":63,"code":64,"language":65,"meta":66,"style":66},"language-robot shiki shiki-themes github-light github-dark","*** Test Cases ***\nThe Buyer Sees The Seller's Price Change\n    New Browser    chromium\n    ${seller} =    New Context\n    ${buyer} =     New Context\n\n    Switch Context    ${seller}\n    New Page    ${SHOP}\u002Fadmin\n    Log In As    seller@example.com\n    Fill Text    id=price    99.00\n    Click    text=Publish\n\n    Switch Context    ${buyer}\n    New Page    ${SHOP}\u002Fitem\u002F42\n    Get Text    css=.price    ==    99.00\n","robot","",[48,68,69,77,83,89,95,101,108,114,120,126,132,138,143,149,155],{"__ignoreMap":66},[70,71,74],"span",{"class":72,"line":73},"line",1,[70,75,76],{},"*** Test Cases ***\n",[70,78,80],{"class":72,"line":79},2,[70,81,82],{},"The Buyer Sees The Seller's Price Change\n",[70,84,86],{"class":72,"line":85},3,[70,87,88],{},"    New Browser    chromium\n",[70,90,92],{"class":72,"line":91},4,[70,93,94],{},"    ${seller} =    New Context\n",[70,96,98],{"class":72,"line":97},5,[70,99,100],{},"    ${buyer} =     New Context\n",[70,102,104],{"class":72,"line":103},6,[70,105,107],{"emptyLinePlaceholder":106},true,"\n",[70,109,111],{"class":72,"line":110},7,[70,112,113],{},"    Switch Context    ${seller}\n",[70,115,117],{"class":72,"line":116},8,[70,118,119],{},"    New Page    ${SHOP}\u002Fadmin\n",[70,121,123],{"class":72,"line":122},9,[70,124,125],{},"    Log In As    seller@example.com\n",[70,127,129],{"class":72,"line":128},10,[70,130,131],{},"    Fill Text    id=price    99.00\n",[70,133,135],{"class":72,"line":134},11,[70,136,137],{},"    Click    text=Publish\n",[70,139,141],{"class":72,"line":140},12,[70,142,107],{"emptyLinePlaceholder":106},[70,144,146],{"class":72,"line":145},13,[70,147,148],{},"    Switch Context    ${buyer}\n",[70,150,152],{"class":72,"line":151},14,[70,153,154],{},"    New Page    ${SHOP}\u002Fitem\u002F42\n",[70,156,158],{"class":72,"line":157},15,[70,159,160],{},"    Get Text    css=.price    ==    99.00\n",[25,162,163],{},"Two contexts are two isolated profiles — separate cookies, separate storage,\nseparate logins — in one browser, each created in a few milliseconds. Cypress has one\nbrowser session per test and no way to run two of them side by side, so this\nscenario is not something you write awkwardly there. It is something you do not\nwrite.",[165,166],"doc-table",{":head":167,":rows":168},"[\"\",\"Browser\",\"Cypress\"]","[[\"Runs\",\"Outside the browser, over Playwright\",\"Inside the browser, in the app's event loop\"],[\"Engines\",\"Chromium, Firefox and WebKit\",\"Chromium family, Firefox, WebKit (experimental)\"],[\"Second origin in one test\",\"Just navigate\",\"`cy.origin()`\"],[\"Multiple tabs\",\"`New Page` again\",\"Not supported\"],[\"Two browsers at once\",\"`New Browser` twice\",\"Not supported\"],[\"Two logged-in users interacting\",\"Two contexts, switched between\",\"Not supported\"]]",[170,171,172],"doc-note",{},[25,173,174],{},"WebKit matters more than the row suggests. It is the engine behind Safari, and\ntherefore every browser on iOS.",[37,176,178],{"id":177},"_2-robot-framework-tests-everything-not-only-the-web","2. Robot Framework tests everything, not only the web",[25,180,181],{},"This is the one that decides most real projects, and it has nothing to do with\nbrowsers.",[25,183,184,185,188],{},"Your system is not a web page. It is a web page ",[56,186,187],{},"and"," a REST API, a database,\na message queue, an SFTP drop, a PDF, a mainframe screen, a Windows desktop\nclient, a mobile app, a piece of hardware on a serial port. A test that logs in\nthrough the UI and verifies the result in the database is an ordinary\nrequirement, not an exotic one.",[25,190,191],{},"Robot Framework has libraries for all of that, and they compose in the same\ntest, in the same syntax, in the same report.",[25,193,194],{},"If a library does not exist for your particular thing, you write one, and the\nbar is far lower than people expect. No class, no packaging, no publishing: a\nPython file next to your tests, with a function in it, is a library. Every\nfunction becomes a keyword.",[61,196,201],{"className":197,"code":198,"filename":199,"language":200,"meta":66,"style":66},"language-python shiki shiki-themes github-light github-dark","def the_invoice_total_in_the_database(order_id):\n    ...\n    return total\n","my_stuff.py","python",[48,202,203,208,213],{"__ignoreMap":66},[70,204,205],{"class":72,"line":73},[70,206,207],{},"def the_invoice_total_in_the_database(order_id):\n",[70,209,210],{"class":72,"line":79},[70,211,212],{},"    ...\n",[70,214,215],{"class":72,"line":85},[70,216,217],{},"    return total\n",[61,219,221],{"className":63,"code":220,"language":65,"meta":66,"style":66},"*** Settings ***\nLibrary     my_stuff.py\n\n*** Test Cases ***\nExample\n    ${total} =    The Invoice Total In The Database    ${order_id}\n",[48,222,223,228,233,237,241,246],{"__ignoreMap":66},[70,224,225],{"class":72,"line":73},[70,226,227],{},"*** Settings ***\n",[70,229,230],{"class":72,"line":79},[70,231,232],{},"Library     my_stuff.py\n",[70,234,235],{"class":72,"line":85},[70,236,107],{"emptyLinePlaceholder":106},[70,238,239],{"class":72,"line":91},[70,240,76],{},[70,242,243],{"class":72,"line":97},[70,244,245],{},"Example\n",[70,247,248],{"class":72,"line":103},[70,249,250],{},"    ${total} =    The Invoice Total In The Database    ${order_id}\n",[25,252,253],{},"That is the entire mechanism. Publish it to PyPI later if other teams want it —\nor never, because most of these are three functions that only make sense in one\ncompany.",[25,255,256,257,260],{},"Cypress tests web pages, which is what it is for. When your test needs to check\nthe database, you write a task in Node and call it through ",[48,258,259],{},"cy.task"," — at which\npoint you are maintaining a small integration framework of your own, and it\nreports nothing to the log.",[37,262,264],{"id":263},"_3-keyword-driven-logged-parallel-and-open-source-in-the-whole","3. Keyword-driven, logged, parallel, and open source in the whole",[25,266,267],{},"Four things, all of which follow from Robot Framework being a test automation\nframework rather than a web-test runner.",[25,269,270,273,274,277,278,281],{},[56,271,272],{},"Compound keywords are real abstractions."," You write ",[48,275,276],{},"Given the user has an overdrawn account",", and it appears in the log ",[56,279,280],{},"as a step",", with everything it\ndid nested underneath it, collapsible. A JavaScript helper function is a\nfunction: it runs, and the log shows whatever the commands inside it happened to\nprint, flat. This is the difference between a report a business analyst can read\nand a report only its author can.",[25,283,284,287,288,295],{},[56,285,286],{},"Parallelism."," ",[289,290,294],"a",{"href":291,"rel":292},"https:\u002F\u002Fgithub.com\u002Fmkorpela\u002Fpabot",[293],"nofollow","Pabot"," splits a suite across\nprocesses, on your laptop or on one CI runner, and merges the results. It is\nfree and it is local. Cypress parallelisation is a feature of Cypress Cloud —\na paid, hosted service. You can hand-roll it with your own CI matrix, and many\nteams do, but the batteries are not included.",[25,297,298,301],{},[56,299,300],{},"Open source through and through."," Robot Framework, Browser library, Pabot and\nthe report at the end are all Apache 2.0, and there is no tier above them.\nCypress the runner is MIT; the dashboard, the parallelisation and the flake\nanalytics are the product. That is a legitimate business model. It is worth\nknowing which parts of your workflow you own and which you rent.",[25,303,304,307,308,311],{},[56,305,306],{},"Made for end-to-end testing."," Robot Framework has been running acceptance\ntests since 2005, when it was built at Nokia; it was open-sourced in 2008. Two\ndecades of people meaning ",[32,309,310],{},"acceptance"," in the contractual sense.",[25,313,314],{},"And end-to-end means end to end. A real one crosses systems — the web shop, the\npayment provider's API, the warehouse database, the confirmation email — and\noften crosses users and pages as well. Cypress describes itself as an\nend-to-end testing tool, and within one browser session on one origin it is a\nvery good one. But an end that stops at the edge of the browser tab is not the\nend of anything: the moment a scenario needs a second user, a second origin or\na system that is not a web page, the tool is not able to express it. Robot\nFramework treats all of those as ordinary.",[37,316,318],{"id":317},"_4-the-test-reads-like-the-thing-it-tests","4. The test reads like the thing it tests",[25,320,321],{},"Look at both files. Not at the line count — at what you have to know to read\nthem.",[323,324],"comparison",{":notes":325,"left":326,"right":327},"[\"Both wait automatically; neither needs an explicit sleep.\",\"Browser puts the assertion inside the keyword — Get Text reads and checks in one line.\",\"Cypress chains commands that are queued rather than executed where they appear, which is why `await` does not work as you expect in a Cypress test.\"]","comparison\u002Fcypress\u002Ftest.robot","comparison\u002Fcypress\u002Ftest.cy.js",[25,329,330,331,334,335,338,339,342,343,346],{},"The Robot Framework file is readable by someone who has never used Robot\nFramework. The JavaScript file is readable by someone who knows JavaScript,\nknows that ",[48,332,333],{},"cy"," commands enqueue rather than execute, knows why you cannot\n",[48,336,337],{},"await"," them, and knows what ",[48,340,341],{},"should('contain', ...)"," does differently from\n",[48,344,345],{},"should('have.text', ...)",".",[25,348,349],{},"That difference compounds. Tests are read far more often than they are written,\nand usually by more people than wrote them.",[37,351,353],{"id":352},"when-cypress-is-the-right-answer","When Cypress is the right answer",[25,355,356],{},"Plainly: your team is front-end developers, the tests live beside the components\nthey cover, nobody outside the team reads the results, and the system under test\nis a web application and nothing else. The Cypress runner is genuinely good to\ndevelop against, and picking the tool your team already speaks is a real\nargument.",[170,358,360],{"kind":359},"aside",[25,361,362,365,366,370,371,373],{},[56,363,364],{},"PS."," Even then, have a look at ",[289,367,369],{"href":368},"\u002Fwhy\u002Fvs-playwright","Playwright Test"," before\nyou commit. It is also JavaScript, also lives next to your components, and it\nis free of the architectural constraints described above — no ",[48,372,50],{},", no\none-session-per-test, three real engines. If the answer to \"which JavaScript\nrunner?\" is being decided today, it deserves to be in the comparison.",[25,375,376],{},"Outside those conditions — more than one technology, more than one audience for\nthe report, testers who are not JavaScript developers — the constraints start to\ncost you, and Robot Framework was built for the job you actually have.",[170,378,379],{},[25,380,381,382,386,387,392],{},"Facts on this page are checked against Cypress 15 and Browser ",[383,384],"ver",{"name":385},"browser",". If\nsomething here has become wrong,\n",[289,388,391],{"href":389,"rel":390},"https:\u002F\u002Fgithub.com\u002FMarketSquare\u002Frobotframework-browser\u002Fissues",[293],"tell us"," —\na comparison page that quietly rots is worse than none.",[394,395,396],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":66,"searchDepth":79,"depth":79,"links":398},[399,400,401,402,403],{"id":39,"depth":79,"text":40},{"id":177,"depth":79,"text":178},{"id":263,"depth":79,"text":264},{"id":317,"depth":79,"text":318},{"id":352,"depth":79,"text":353},"Cypress 15, Browser %%browser%%","md",{},"\u002Fwhy\u002Fcypress",{"title":20,"description":27},"vs-cypress","why\u002Fcypress","A JavaScript end-to-end runner that lives inside the browser it tests.","Cypress","mF1fkPN8YYeswBecnRvV-jnU23DAtEa8xyFmi_Hd9oE",{"id":415,"title":416,"body":417,"comparedAgainst":612,"description":613,"extension":405,"meta":614,"navigation":106,"order":79,"path":615,"seo":616,"slug":617,"stem":618,"tagline":619,"tool":369,"__hash__":620},"why\u002Fwhy\u002Fplaywright.md","Browser vs Playwright Test",{"type":22,"value":418,"toc":601},[419,426,436,440,443,458,461,470,474,477,482,485,490,508,512,515,521,525,528,550,553,557,560,564,571,575,578,588,591,595,598],[25,420,421,422,425],{},"This is not really a competition, because ",[56,423,424],{},"Browser library is built on\nPlaywright",". Every keyword here ends up as a Playwright call. When Playwright\ngets faster, we get faster. When it gains an API, we can expose it.",[25,427,428,429,431,432,435],{},"So the honest comparison is not Browser against Playwright. It is Robot\nFramework against ",[56,430,369],{},", the TypeScript test runner — two\ndifferent answers to ",[32,433,434],{},"how do you write and organise tests",", over the same\nautomation engine.",[37,437,439],{"id":438},"what-we-do-not-have","What we do not have",[25,441,442],{},"First, because a comparison page that lists only its own strengths is marketing.",[25,444,445,446,449,450,453,454,457],{},"We have the Playwright ",[56,447,448],{},"API",". We do not have Playwright ",[56,451,452],{},"Test",". Concretely,\n",[48,455,456],{},"expect()"," and its web-first assertions do not map to Browser keywords 1:1.\nPlaywright's assertion library is large, specific and very well made; Browser's\ngetters cover the same ground for the things people actually assert, but they\nare not a drop-in translation.",[25,459,460],{},"Nor do we have the Playwright Test runner's fixtures, projects, sharded\nparallelism or HTML report. Robot Framework has its own answers to all of those\n— but they are Robot Framework's answers, so porting a Playwright Test suite is\na rewrite rather than a translation.",[170,462,463],{},[25,464,465,466,469],{},"If your test looks like ",[48,467,468],{},"await expect(locator).toHaveText('x')"," and that is\nexactly how you want it to look, you are already using the right tool. This page\nis for people choosing, not for people migrating away from something that works.",[37,471,473],{"id":472},"what-we-offer-instead","What we offer instead",[25,475,476],{},"Four things, all of which come from Robot Framework rather than from us.",[478,479,481],"h3",{"id":480},"_1-readability","1. Readability",[25,483,484],{},"The same test, written both ways:",[323,486],{":notes":487,"left":488,"right":489},"[\"Same engine underneath, same auto-waiting, same role selectors.\",\"The Robot Framework file has no async\u002Fawait, no braces, no arrow functions and no fixture argument.\"]","comparison\u002Fplaywright\u002Ftest.robot","comparison\u002Fplaywright\u002Ftest.spec.ts",[25,491,492,493,496,497,500,501,503,504,507],{},"The right-hand file is good TypeScript. The point is that it ",[32,494,495],{},"is"," TypeScript: to\nread it you need to know what ",[48,498,499],{},"async"," means, why every line needs ",[48,502,337],{},", what\ndestructuring ",[48,505,506],{},"{ page }"," does, and what a fixture is. The left-hand file needs\nnone of that.",[478,509,511],{"id":510},"_2-easier-to-learn","2. Easier to learn",[25,513,514],{},"A tester who has never automated anything can write a useful Robot Framework\ntest on their first day. The syntax is a keyword, then its arguments, separated\nby spaces.",[25,516,517,518,520],{},"Reaching the same point in Playwright Test means learning JavaScript's\nasynchronous model first — not the syntax, the model. A missing ",[48,519,337],{}," is a\ntest that passes while the application is broken, and it looks nothing like a\nmistake.",[478,522,524],{"id":523},"_3-business-logic-expressed-as-business-language","3. Business logic, expressed as business language",[25,526,527],{},"Keyword-driven testing is the real feature here, and it is bigger than\nreadability.",[25,529,530,531,534,535,534,538,541,542,545,546,549],{},"You build vocabulary. ",[48,532,533],{},"Log in as an approved supplier",", ",[48,536,537],{},"Submit a claim over the excess",[48,539,540],{},"The claim should be routed to manual review"," — each a keyword,\ncomposed of other keywords, all the way down to ",[48,543,544],{},"Click",". The test reads as the\nbusiness process, and ",[56,547,548],{},"the log reads the same way",": every compound keyword is\na collapsible step in the report, not a flattened list of clicks.",[25,551,552],{},"That is what makes a Robot Framework report something you can hand to a domain\nexpert. Not the styling — the structure.",[478,554,556],{"id":555},"_4-cross-technology-testing","4. Cross-technology testing",[25,558,559],{},"Robot Framework is a general-purpose automation framework that happens to be\nvery good at web. One suite, one report, one syntax across:",[165,561],{":head":562,":rows":563},"[\"Also in the same test\",\"With\"]","[[\"REST and GraphQL APIs\",\"RequestsLibrary, and others\"],[\"Databases\",\"DatabaseLibrary\"],[\"Mobile apps\",\"AppiumLibrary\"],[\"Desktop applications\",\"FlaUI, SikuliX, RoboSAPiens for SAP\"],[\"Files, SFTP, SSH, archives\",\"OperatingSystem, SSHLibrary, ArchiveLibrary\"],[\"Anything else\",\"A Python class you write in an afternoon\"]]",[25,565,566,567,570],{},"Playwright Test tests web applications. You ",[32,568,569],{},"can"," call an API from a Playwright\ntest — the request fixture is good — but when one scenario spans a web UI, a\nmessage queue and a mainframe, you are assembling a framework. Robot Framework\nalready is one.",[37,572,574],{"id":573},"our-clear-recommendation","Our clear recommendation",[25,576,577],{},"We mean this, and we are not burying it in small type at the bottom:",[170,579,580,585],{"kind":359},[25,581,582],{},[56,583,584],{},"If you are looking for a great TypeScript web automation framework, and you do\nnot need to test other technologies, and you have people who read and write\nTypeScript, and you have no requirement for business-readable test reports —\nthen our clear recommendation is Playwright Test.",[25,586,587],{},"It is excellent. Use it.",[25,589,590],{},"Those four conditions are the whole decision. Break any one of them — a tester\nwho is not a developer, a system that is not only a web application, an auditor\nwho has to read the report — and Robot Framework starts earning its keep. Meet\nall four, and Playwright Test is the better fit, and we would rather you were\nproductive than loyal.",[37,592,594],{"id":593},"thank-you","Thank you",[25,596,597],{},"Browser library exists because Playwright exists. The auto-waiting, the role\nselectors, the trace viewer, three engines under one API — we did not build any\nof that. We made it available to Robot Framework.",[25,599,600],{},"Best regards from our side to an awesome open-source project.",{"title":66,"searchDepth":79,"depth":79,"links":602},[603,604,610,611],{"id":438,"depth":79,"text":439},{"id":472,"depth":79,"text":473,"children":605},[606,607,608,609],{"id":480,"depth":85,"text":481},{"id":510,"depth":85,"text":511},{"id":523,"depth":85,"text":524},{"id":555,"depth":85,"text":556},{"id":573,"depth":79,"text":574},{"id":593,"depth":79,"text":594},"Playwright %%playwright%%, Browser %%browser%%","This is not really a competition, because Browser library is built on\nPlaywright. Every keyword here ends up as a Playwright call. When Playwright\ngets faster, we get faster. When it gains an API, we can expose it.",{},"\u002Fwhy\u002Fplaywright",{"title":416,"description":613},"vs-playwright","why\u002Fplaywright","Microsoft's browser automation framework, and its TypeScript test runner.","A_prUut2MYpwYAPjPP81FQatPRicfs0Ca3D4AGlVvag",{"id":622,"title":623,"body":624,"comparedAgainst":861,"description":628,"extension":405,"meta":862,"navigation":106,"order":85,"path":863,"seo":864,"slug":865,"stem":866,"tagline":867,"tool":708,"__hash__":868},"why\u002Fwhy\u002Fseleniumlibrary.md","Browser vs SeleniumLibrary",{"type":22,"value":625,"toc":851},[626,629,632,645,649,654,657,661,665,672,676,679,704,724,728,735,739,743,757,764,768,771,774,778,781,784,787,791,794,825,828,832,836,839],[25,627,628],{},"Some of us maintained SeleniumLibrary for years before starting Browser, and its\ncurrent maintainers are our friends. It is a good library, it is actively\nmaintained, and for a great many projects it is the right answer.",[25,630,631],{},"So this page is facts, not persuasion. Two libraries, two underlying\ntechnologies, and what actually differs.",[170,633,634],{},[25,635,636,637,640,641,644],{},"Both are Robot Framework libraries, so this really is like for like. The\ndifferences below come mostly from the layer underneath: ",[56,638,639],{},"WebDriver"," for\nSeleniumLibrary, ",[56,642,643],{},"Playwright"," for Browser.",[37,646,648],{"id":647},"the-same-test-both-ways","The same test, both ways",[323,650],{":notes":651,"left":652,"right":653},"[\"Selenium's own \\\"eight components\\\" example, written with each library.\",\"Browser's getters assert, so Get Text does in one line what Wait Until Element Is Visible plus Element Text Should Be does in two.\",\"Neither file is unreadable. This is not the interesting difference.\"]","comparison\u002Fseleniumlibrary\u002Fbrowser.robot","comparison\u002Fseleniumlibrary\u002Fseleniumlibrary.robot",[25,655,656],{},"The code is not where the gap is. The gap is underneath.",[37,658,660],{"id":659},"how-the-two-talk-to-the-browser","How the two talk to the browser",[165,662],{":head":663,":rows":664},"[\"\",\"Browser (Playwright)\",\"SeleniumLibrary (WebDriver)\"]","[[\"Protocol\",\"One persistent WebSocket connection to the browser\",\"W3C WebDriver, an HTTP request per command\"],[\"What it launches\",\"Browser builds Playwright downloads and pins\",\"The browser installed on the machine, through a driver\"],[\"Extra binaries\",\"`rfbrowser init` downloads browsers; no per-browser driver\",\"A driver per browser, resolved by Selenium Manager since 4.6\"],[\"Version coupling\",\"Browsers are pinned to the Playwright version, so they match by construction\",\"Driver and browser must stay in step as the browser auto-updates\"]]",[25,666,667,668,671],{},"The protocol difference is the origin of most of the rest. A command per HTTP\nround trip means every element lookup, every check and every retry is another\nrequest; it also means the browser can change between two of them, which is\nwhere ",[48,669,670],{},"StaleElementReferenceException"," comes from.",[37,673,675],{"id":674},"waiting","Waiting",[25,677,678],{},"This is the single largest practical difference.",[25,680,681,684,685,534,688,691,692,695,696,699,700,703],{},[56,682,683],{},"Browser"," performs Playwright's actionability checks before every action. For a\nclick, all five must pass: the element is ",[56,686,687],{},"visible",[56,689,690],{},"stable"," (unmoved for\ntwo animation frames), ",[56,693,694],{},"receives events"," (nothing is on top of it), ",[56,697,698],{},"enabled",",\nand for typing also ",[56,701,702],{},"editable",". It retries until they do or the timeout\nexpires, and the failure names the check that never passed.",[25,705,706,709,710,713,714,717,718,723],{},[56,707,708],{},"SeleniumLibrary"," offers an implicit wait — a global timeout applied to element\nlookup, and lookup only — plus explicit ",[48,711,712],{},"Wait Until ..."," keywords you place\nyourself. Selenium waits for ",[48,715,716],{},"document.readyState"," on navigation, which, as\n",[289,719,722],{"href":720,"rel":721},"https:\u002F\u002Fwww.selenium.dev\u002Fdocumentation\u002Fwebdriver\u002Fwaits\u002F",[293],"its own documentation notes",",\n\"only concerns itself with loading assets defined in the HTML\" — a\nJavaScript-rendered page is not covered.",[165,725],{":head":726,":rows":727},"[\"\",\"Browser\",\"SeleniumLibrary\"]","[[\"Before an action\",\"Five actionability checks, automatically\",\"Nothing beyond the implicit lookup wait\"],[\"Element under an overlay\",\"Waits for the overlay to go, then clicks\",\"Clicks, and the overlay receives it\"],[\"Element still animating\",\"Waits for it to stop\",\"Clicks mid-animation\"],[\"DOM re-render between lookup and use\",\"Locator re-resolves\",\"`StaleElementReferenceException`\"],[\"Explicit waits in a normal test\",\"Rare\",\"Common\"]]",[25,729,730,731,734],{},"That last row is the one you feel. ",[48,732,733],{},"Wait Until Element Is Visible"," before a\nclick is a line SeleniumLibrary suites are full of, and Browser suites do not\nhave.",[37,736,738],{"id":737},"selectors","Selectors",[165,740],{":head":741,":rows":742},"[\"Strategy\",\"Browser\",\"SeleniumLibrary\"]","[[\"CSS, XPath, id, name, class, tag\",\"Yes\",\"Yes\"],[\"Link text\",\"Via `text=`\",\"`link:` and `partial link:`\"],[\"By accessible role and name (`role=`)\",\"Yes\",\"No equivalent\"],[\"Visible text as a first-class engine (`text=`)\",\"Yes, substring or exact\",\"Anchors only\"],[\"Chaining several strategies\",\"`>>`\",\"`>>`\"],[\"Open shadow DOM\",\"Pierced automatically\",\"Not a locator strategy; needs the shadow root through JavaScript\"],[\"Crossing into an iframe\",\"`>>>` inside the same selector\",\"`Select Frame`, then `Unselect Frame`\"],[\"More than one match\",\"Strict mode by default — an error\",\"First match wins\"]]",[25,744,745,746,749,750,752,753,756],{},"Role selectors are the substantive addition. ",[48,747,748],{},"role=button[name=\"Save\"]"," matches\nwhat the element ",[32,751,495],{}," and what it is ",[32,754,755],{},"called"," — the two things a screen-reader\nuser navigates by — so it survives a redesign that moves the button and rewrites\nthe markup around it. There is no WebDriver equivalent; you approximate it with\nXPath over ARIA attributes.",[25,758,759,760,763],{},"Shadow DOM is the other. Selenium 4 can reach a shadow root via ",[48,761,762],{},"getShadowRoot()","\nper host, so it is possible; it is not a selector, so it does not compose with\nthe rest of a locator, and it does not nest without work.",[37,765,767],{"id":766},"diagnosing-a-failure","Diagnosing a failure",[165,769],{":head":726,":rows":770},"[[\"Screenshot on failure\",\"Yes, in the Robot Framework log\",\"Yes, in the Robot Framework log\"],[\"Video of the run\",\"`New Context` with `recordVideo=…`\",\"Not built in\"],[\"Trace\",\"`New Context` with `tracing=True` — DOM snapshots per action, network, console, timeline\",\"No equivalent\"],[\"Network inspection\",\"Built in, including request interception\",\"Via CDP, Chromium only\"]]",[25,772,773],{},"The trace is the part with no counterpart. A Playwright trace is a recording you\nopen afterwards and step through: for every action, the DOM as it was, with\nDevTools over it, plus the network and console at that moment. It is the\ndifference between \"the click failed\" and seeing the element you meant to click\nsitting behind a cookie banner.",[37,775,777],{"id":776},"browsers-and-platforms","Browsers and platforms",[25,779,780],{},"Where SeleniumLibrary is genuinely ahead — and it is worth being straight about\nthis, because it decides real projects:",[165,782],{":head":726,":rows":783},"[[\"Engines\",\"Chromium, Firefox, WebKit\",\"Chrome, Edge, Firefox, Safari, and more via WebDriver\"],[\"The actual shipped browser\",\"Playwright's builds; real Chrome or Edge via `channel`\",\"Always the real installed browser\"],[\"Safari proper\",\"No — WebKit is the same engine, not the same browser\",\"Yes, safaridriver\"],[\"Internet Explorer, legacy browsers\",\"No\",\"Yes\"],[\"Remote execution\",\"Playwright browser server, or `Connect To Browser`\",\"Selenium Grid, which is mature and widely deployed\"],[\"Mobile\",\"Device emulation\",\"Emulation, and real devices through the Appium ecosystem\"]]",[25,785,786],{},"If you must certify against Safari itself rather than WebKit, or you have an\nestablished Selenium Grid, or a stakeholder requires the vendor's own browser\nunder test, SeleniumLibrary is not the compromise — it is the correct tool.",[37,788,790],{"id":789},"performance","Performance",[25,792,793],{},"We are not going to publish a benchmark, because a benchmark of two tools on one\nsuite mostly measures the suite. The structural reasons Browser tends to be\nfaster are stateable without one:",[795,796,797,804,810,819],"ul",{},[798,799,800,803],"li",{},[56,801,802],{},"One connection instead of many requests."," Every WebDriver command is an\nHTTP round trip; Playwright multiplexes over a single WebSocket.",[798,805,806,809],{},[56,807,808],{},"No per-element round trips for waiting."," Actionability is evaluated in the\nbrowser rather than polled from outside.",[798,811,812,287,815,818],{},[56,813,814],{},"Contexts instead of browsers for isolation.",[48,816,817],{},"New Context"," gives a clean,\nisolated profile in a few milliseconds; a fresh Selenium session starts a browser.",[798,820,821,824],{},[56,822,823],{},"One Node process for all three engines",", rather than a driver process per\nbrowser.",[25,826,827],{},"Flakiness follows the same logic. Most of it comes from acting on an element\nthat was not ready, and the actionability checks remove that category rather\nthan mitigating it.",[37,829,831],{"id":830},"choosing","Choosing",[165,833],{":head":834,":rows":835},"[\"Choose\",\"When\"]","[[\"SeleniumLibrary\",\"You need Safari, IE or another WebDriver-only browser; you have a Selenium Grid; you have a large suite that works, and rewriting it buys nothing; your organisation standardises on WebDriver.\"],[\"Browser\",\"You are starting fresh; your app is a JavaScript-heavy SPA; you keep writing explicit waits; you need shadow DOM, iframes or role selectors; you want traces and video; suite runtime matters.\"]]",[25,837,838],{},"There is no obligation to migrate. Both libraries are maintained, both are\nRobot Framework libraries, and they can live in the same project — one suite on\neach, sharing everything else. Starting Browser has never required abandoning\nSeleniumLibrary.",[170,840,841],{},[25,842,843,844,846,847,850],{},"Checked against SeleniumLibrary 6.7 on Selenium 4 and Browser ",[383,845],{"name":385},". If\nanything here is wrong or has aged badly,\n",[289,848,391],{"href":389,"rel":849},[293]," —\nwe would rather fix it than let an unfair comparison stand.",{"title":66,"searchDepth":79,"depth":79,"links":852},[853,854,855,856,857,858,859,860],{"id":647,"depth":79,"text":648},{"id":659,"depth":79,"text":660},{"id":674,"depth":79,"text":675},{"id":737,"depth":79,"text":738},{"id":766,"depth":79,"text":767},{"id":776,"depth":79,"text":777},{"id":789,"depth":79,"text":790},{"id":830,"depth":79,"text":831},"SeleniumLibrary 6.7 on Selenium 4, Browser %%browser%%",{},"\u002Fwhy\u002Fseleniumlibrary",{"title":623,"description":628},"vs-seleniumlibrary","why\u002Fseleniumlibrary","The long-standing Robot Framework web library, built on Selenium WebDriver.","fC6L_8XvYwwdbjkuNy-FUYVe2EoeEobiwGk2JkqRQsg",[870,873,875],{"to":871,"label":872},"\u002Fwhy\u002Fvs-cypress","vs Cypress",{"to":368,"label":874},"vs Playwright Test",{"to":876,"label":877},"\u002Fwhy\u002Fvs-seleniumlibrary","vs SeleniumLibrary",[879],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># No Sleep. No Wait Until Element Is Visible.\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">       text=Sign in\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Fill Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">   id=search    robot\u003C\u002Fspan>\u003C\u002Fspan>",[881],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">            h1            ==        Welcome\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Title\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">           contains      Robot\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Element Count\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">   .row          >         3\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Attribute\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">       a#next  href  matches   \u002Fpage\u002F\\d+\u003C\u002Fspan>\u003C\u002Fspan>",[883],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">     text=Sign in\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">     \"Sign in\" >> xpath=..\u002Finput\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">  iframe#preview >>> h1\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">  css=my-widget >> css=button\u003C\u002Fspan>\u003C\u002Fspan>",[885],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">New Browser\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    chromium    headless=True\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">New Browser\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    firefox\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">New Browser\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    webkit\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">New Context\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    viewport={'width': 390, 'height': 844}    locale=de-DE\u003C\u002Fspan>\u003C\u002Fspan>",[887],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">New Context\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">   tracing=True    videosPath=videos\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">New Browser\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">   chromium\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># On failure the trace and video land in the log.\u003C\u002Fspan>\u003C\u002Fspan>",1786734136973]