base on Headless Chrome/Chromium driver for Capybara # Cuprite - Headless Chrome driver for Capybara Cuprite is a pure Ruby driver (read as _no_ Selenium/WebDriver/ChromeDriver dependency) for [Capybara](https://github.com/teamcapybara/capybara). It allows you to run Capybara tests on a headless Chrome or Chromium. Under the hood it uses [Ferrum](https://github.com/rubycdp/ferrum#index) which is high-level API to the browser by CDP protocol. ## Install Add this to your `Gemfile` and run `bundle install`. ``` ruby group :test do gem "cuprite" end ``` In your test setup add: ``` ruby require "capybara/cuprite" Capybara.javascript_driver = :cuprite Capybara.register_driver(:cuprite) do |app| Capybara::Cuprite::Driver.new(app, window_size: [1200, 800]) end ``` if you use `Docker` don't forget to pass `no-sandbox` option: ```ruby Capybara::Cuprite::Driver.new(app, browser_options: { 'no-sandbox': nil }) ``` Since Cuprite uses [Ferrum](https://github.com/rubycdp/ferrum#examples) there are many useful methods you can call even using this driver: ```ruby browser = page.driver.browser browser.mouse.move(x: 123, y: 456).down.up ``` For Selenium you better check your code for `manage` calls because it works differently in Cuprite, see the documentation below. ## Customization See the full list of options for [Ferrum](https://github.com/rubycdp/ferrum#customization). You can pass options with the following code in your test setup: ``` ruby Capybara.register_driver(:cuprite) do |app| Capybara::Cuprite::Driver.new(app, options) end ``` `Cuprite`-specific options are: * options `Hash` * `:url_blacklist` (Array) - array of regexes to match against requested URLs * `:url_whitelist` (Array) - array of regexes to match against requested URLs ## Debugging You can put `page.driver.debug` or `page.driver.debug(binding)` in your test to pause it. This will launch the browser where you can inspect the content. ```ruby it "does something useful" do visit root_path fill_in "field", with: "value" page.driver.debug(binding) expect(page).to have_content("value") end ``` In the middle of the execution Chrome will open a new tab where you can inspect the content and also if you passed `binding` an `irb` or `pry` console will be opened where you can further experiment with the test. ## Clicking/Scrolling * `page.driver.click(x, y)` Click a very specific area of the screen. * `page.driver.scroll_to(left, top)` Scroll to a given position. * `element.send_keys(*keys)` Send keys to a given node. ## Request headers Manipulate HTTP request headers like a boss: ``` ruby page.driver.headers # => {} page.driver.headers = { "User-Agent" => "Cuprite" } page.driver.add_headers("Referer" => "https://example.com") page.driver.headers # => { "User-Agent" => "Cuprite", "Referer" => "https://example.com" } ``` Notice that `headers=` will overwrite already set headers. You should use `add_headers` if you want to add a few more. These headers will apply to all subsequent HTTP requests (including requests for assets, AJAX, etc). They will be automatically cleared at the end of the test. ## Network traffic * `page.driver.network_traffic` allows you to inspect network traffic (i.e., loaded resources) on the current page. It returns an array of `Ferrum::Network::Exchange` objects, each representing a network request/response exchange. You can query both the request and response details of each exchange. ```ruby # Retrieve all network exchanges network_traffic = page.driver.network_traffic # Access the first exchange first_exchange = network_traffic.first # Inspect the response of the first request response = first_exchange.response ``` * `page.driver.wait_for_network_idle` Natively waits for network idle and if there are no active connections returns or raises `TimeoutError` error. Accepts the same options as [`wait_for_idle`](https://github.com/rubycdp/ferrum#wait_for_idleoptions) ```ruby page.driver.wait_for_network_idle page.driver.refresh ``` Please note that network traffic is not cleared when you visit new page. You can manually clear the network traffic by calling `page.driver.clear_network_traffic` or `page.driver.reset` * `page.driver.wait_for_reload` unlike `wait_for_network_idle` will wait until the whole page is reloaded or raise a timeout error. It's useful when you know that for example after clicking autocomplete suggestion you expect page to be reloaded, you have a few choices - put sleep or wait for network idle, but both are bad. Sleep makes you wait longer or less than needed, network idle can return earlier even before the whole page is started to reload. Here's the rescue. ## Manipulating cookies The following methods are used to inspect and manipulate cookies: * `page.driver.cookies` - a hash of cookies accessible to the current page. The keys are cookie names. The values are `Cookie` objects, with the following methods: `name`, `value`, `domain`, `path`, `size`, `secure?`, `httponly?`, `session?`, `expires`. * `page.driver.set_cookie(name, value, options = {})` - set a cookie. The options hash can take the following keys: `:domain`, `:path`, `:secure`, `:httponly`, `:expires`. `:expires` should be a `Time` object. * `page.driver.remove_cookie(name)` - remove a cookie * `page.driver.clear_cookies` - clear all cookies ## Screenshot Besides capybara screenshot method you can get image as Base64: * `page.driver.render_base64(format, options)` ## Authorization * `page.driver.basic_authorize(user, password)` ## Proxy * `page.driver.set_proxy(ip, port, user, password)` ## URL Blocklisting & Allowlisting Cuprite supports URL blocklisting, which allows you to prevent scripts from running on designated domains: ```ruby page.driver.browser.url_blocklist = %r{http://www.example.com} ``` and also URL allowlisting, which allows scripts to only run on designated domains: ```ruby page.driver.browser.url_allowlist = %r{http://www.example.com} ``` For legacy support, `url_blacklist=` and `url_whitelist=` continue to work respectively. If you are experiencing slower run times, consider creating a URL allowlist of domains that are essential or a blocklist of domains that are not essential, such as ad networks or analytics, to your testing environment. ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). ", Assign "at most 3 tags" to the expected json: {"id":"3249","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"