{"id":21523220,"url":"https://github.com/testcentricity/testcentricity_web","last_synced_at":"2025-04-09T22:42:18.086Z","repository":{"id":32037769,"uuid":"483023833","full_name":"TestCentricity/testcentricity_web","owner":"TestCentricity","description":"TestCentricity™ For Web core POM test automation framework gem","archived":false,"fork":false,"pushed_at":"2025-03-28T22:17:59.000Z","size":29007,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-28T22:35:29.169Z","etag":null,"topics":["automation","cucumber","page-object-model","ruby-gem","selenium","test-automation","web","webdriver"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/testcentricity_web","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TestCentricity.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-18T23:06:15.000Z","updated_at":"2025-03-28T22:18:02.000Z","dependencies_parsed_at":"2024-08-06T02:38:28.237Z","dependency_job_id":null,"html_url":"https://github.com/TestCentricity/testcentricity_web","commit_stats":{"total_commits":288,"total_committers":3,"mean_commits":96.0,"dds":"0.42013888888888884","last_synced_commit":"6fe973ea34dd20890370895a6d90dc071d89b68e"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TestCentricity%2Ftestcentricity_web","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TestCentricity%2Ftestcentricity_web/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TestCentricity%2Ftestcentricity_web/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TestCentricity%2Ftestcentricity_web/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TestCentricity","download_url":"https://codeload.github.com/TestCentricity/testcentricity_web/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248124867,"owners_count":21051757,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","cucumber","page-object-model","ruby-gem","selenium","test-automation","web","webdriver"],"created_at":"2024-11-24T01:13:01.326Z","updated_at":"2025-04-09T22:42:18.061Z","avatar_url":"https://github.com/TestCentricity.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TestCentricity™ For Web\n\n[![Gem Version](https://badge.fury.io/rb/testcentricity_web.svg)](https://badge.fury.io/rb/testcentricity_web)\n[![License (3-Clause BSD)](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg?style=flat-square)](http://opensource.org/licenses/BSD-3-Clause)\n![Gem Downloads](https://img.shields.io/gem/dt/testcentricity_web)\n![Maintained](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg)\n[![Docs](https://img.shields.io/badge/docs-rubydoc-blue.svg)](http://www.rubydoc.info/gems/testcentricity_web)\n\n\nThe TestCentricity™ For Web core framework for desktop and mobile web browser-based app testing implements a Page Object Model\nDSL for use with Cucumber (version 7.x or greater) or RSpec, and Selenium-Webdriver. It also facilitates the configuration\nof the appropriate Selenium-Webdriver capabilities required to establish connections with one or more local or cloud hosted\ndesktop or mobile web browsers.\n\nThe TestCentricity™ For Web gem supports connecting to, and running automated tests against the following target web browsers:\n* locally hosted desktop browsers (Chrome, Edge, Firefox, or Safari)\n* locally hosted \"headless\" Chrome, Firefox, or Edge browsers\n* remote desktop and emulated mobile web browsers hosted on Selenium Grid 4 and Dockerized Selenium Grid 4 environments\n* mobile Safari browsers on iOS device simulators or physical iOS devices (using Appium 2.x and XCode on macOS)\n* mobile Chrome or Android browsers on Android Studio virtual device emulators (using Appium and Android Studio)\n* cloud hosted desktop (Firefox, Chrome, Safari, IE, or Edge) or mobile (iOS Mobile Safari or Android Chrome) web browsers using the following service:\n  * [Browserstack](https://www.browserstack.com/list-of-browsers-and-platforms?product=automate)\n  * [Sauce Labs](https://saucelabs.com/platform/cross-browser-testing)\n  * [TestingBot](https://testingbot.com/features)\n  * [LambdaTest](https://www.lambdatest.com/selenium-automation)\n* web portals utilizing JavaScript front end application frameworks like Ember, React, Angular, and GWT\n* web pages containing HTML5 Video and Audio objects\n* locally hosted emulated iOS Mobile Safari, Android, Windows Phone, or Blackberry mobile browsers (running within a local instance of Chrome)\n\n\n## What's New\n\nA complete history of bug fixes and new features can be found in the {file:CHANGELOG.md CHANGELOG} file.\n\nThe RubyDocs for this gem can be found [here](https://www.rubydoc.info/gems/testcentricity_web).\n\nAn example project that demonstrates the implementation of a page object model framework using TestCentricity™ For Web and\nCucumber can be found [here](https://github.com/TestCentricity/tc_multi_webdriver_sample).\n\n\n### Which gem should I use?\n\n* The [TestCentricity For **Web** gem](https://rubygems.org/gems/testcentricity_web) supports testing of web interfaces via desktop and mobile web browsers\n* The [TestCentricity For **Mobile** gem](https://rubygems.org/gems/testcentricity_mobile) supports testing of native iOS and Android mobile apps\n* The [TestCentricity For **Apps** gem](https://rubygems.org/gems/testcentricity_apps) supports testing of MacOS desktop apps and native iOS and Android mobile apps\n\n| Tested platforms                            | TestCentricity For Web | TestCentricity For Mobile | TestCentricity For Apps |\n|---------------------------------------------|:-:|:-:|:-:|\n| Desktop/mobile web browsers only            | Yes                    | No                        | No                      |\n| Native mobile iOS and/or Android apps only  | No                     | Yes                       | Yes                     |\n| MacOS desktop apps                          | No                     | No                        | Yes                     |\n\n\n## Installation\n\nTestCentricity For Web version 4.6 and above requires Ruby version 3.1.0 or later. To install the TestCentricity For Web gem,\nadd this line to your automation project's `Gemfile`:\n\n    gem 'testcentricity_web'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself using:\n\n    $ gem install testcentricity_web\n\n\n## Setup\n### Using Cucumber\n\nIf you are using Cucumber, you need to require the following in your `env.rb` file:\n```ruby\n    require 'capybara/cucumber'\n    require 'testcentricity_web'\n```\n\n### Using RSpec\n\nIf you are using RSpec instead, you need to require the following in your `spec_helper.rb` file:\n```ruby\n    require 'capybara/rspec'\n    require 'testcentricity_web'\n```\n\n---\n## PageObjects\n\nThe **Page Object Model** is a test automation pattern that aims to create an abstraction of your web app's User Interface\nthat can be used in tests. A **Page Object** represents a single page in your AUT (Application Under Test). **Page Objects**\nencapsulate the implementation details of a web page and expose an API that supports interaction with, and validation of\nthe UI elements on the page.\n\n**Page Objects** makes it easier to maintain automated tests because changes to page UI elements are updated in only one\nlocation - in the **Page Object** class definition. By adopting a **Page Object Model**, Cucumber Feature files and step\ndefinitions are no longer required to hold specific information about a page's UI objects, thus minimizing maintenance\nrequirements. If any element on, or property of a page changes (URL path, text field attributes, button captions, etc.),\nmaintenance is performed in the `PageObject` class definition only, typically with no need to update the affected feature\nfiles, scenarios, or step definitions.\n\n\n### Defining a PageObject\n\nYour `PageObject` class definitions should be contained within individual `.rb` files in the `features/support/pages` folder\nof your test automation project. You define new `PageObjects` as shown below:\n```ruby\n    class LoginPage \u003c TestCentricity::PageObject\n    end\n\n\n    class HomePage \u003c TestCentricity::PageObject\n    end\n\n\n    class RegistrationPage \u003c TestCentricity::PageObject\n    end\n\n\n    class UserAccountPage \u003c TestCentricity::PageObject\n    end\n```\n\n### Adding Traits to a PageObject\n\nWeb pages typically have names and URLs associated with them. Web pages also typically have a unique object or attribute\nthat, when present, indicates that the page's contents have fully loaded.\n\nThe `page_name` trait is registered with the `PageManager` object, which includes a `find_page` method that takes a page\nname as a parameter and returns an instance of the associated `PageObject`. If you intend to use the `PageManager`, you\nmust define a `page_name` trait for each `PageObject` to be registered. Refer to [**section 7 (Instantiating Your PageObjects)**](#instantiating-your-pageobjects).\n\n\nThe `page_name` trait is usually a `String` value that represents the name of the page that will be matched by the `PageManager.findpage`\nmethod. `page_name` traits are case and white-space sensitive. For pages that may be referenced with multiple names, the\n`page_name` trait may also be an `Array` of `String` values representing those page names.\n\nA `page_locator` trait is defined if a page has a unique object or attribute that exists once the page's contents have fully\nloaded. The `page_locator` trait is a CSS or Xpath expression that uniquely identifies the object or attribute. The\n`verify_page_exists` method waits for the `page_locator` trait to exist.\n\nAn optional `page_url` trait should be defined if a page can be directly loaded using a URL. If you set Capybara's `app_host`,\nor specify a base URL when calling the `WebDriverConnect.initialize_web_driver` method, then your `page_url` trait can be the\nrelative URL slug that will be appended to the base URL specified in `app_host`. Specifying a `page_url` trait is optional,\nas not all web pages can be directly loaded via a URL.\n\nYou define your page's **Traits** as shown below:\n```ruby\n    class LoginPage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'Login' }\n      trait(:page_url)     { '/sign_in' }\n      trait(:page_locator) { 'body.login-body' }\n    end\n\n\n    class HomePage \u003c TestCentricity::PageObject\n      # this page may be referred to as 'Home' or 'Dashboard' page so page_name trait is an Array of Strings\n      trait(:page_name)    { ['Home', 'Dashboard'] }\n      trait(:page_url)     { '/dashboard' }\n      trait(:page_locator) { 'body.dashboard' }\n    end\n\n\n    class RegistrationPage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'Registration' }\n      trait(:page_url)     { '/register' }\n      trait(:page_locator) { 'body.registration' }\n    end\n\n\n    class UserAccountPage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'User Account' }\n      trait(:page_url)     { \"/user_account/#{User.current.id}\" }\n      trait(:page_locator) { 'body.useraccount' }\n    end\n```\n\n### Adding UI Elements to a PageObject\n\nWeb pages are made up of UI elements like text fields, check boxes, combo boxes, radio buttons, tables, lists, buttons, etc.\n**UI Elements** are added to your `PageObject` class definition as shown below:\n```ruby\n    class LoginPage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'Login' }\n      trait(:page_url)     { '/sign_in' }\n      trait(:page_locator) { 'body.login-body' }\n\n      # Login page UI elements\n      textfield :user_id_field,       'input#userName'\n      textfield :password_field,      'input#password'\n      button    :login_button,        'button#login'\n      checkbox  :remember_checkbox,   'input#rememberUser'\n      label     :error_message_label, 'div#statusBar.login-error'\n    end\n\n\n    class RegistrationPage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'Registration' }\n      trait(:page_url)     { '/register' }\n      trait(:page_locator) { 'body.registration' }\n\n      # Registration page UI elements\n      textfields  first_name_field:    'input#firstName',\n                  last_name_field:     'input#lastName',\n                  email_field:         'input#email',\n                  phone_number_field:  'input#phone',\n                  address_field:       'input#streetAddress',\n                  city_field:          'input#city',\n                  post_code_field:     'input#postalCode',\n                  password_field:      'input#password',\n                  pword_confirm_field: 'input#passwordConfirmation'\n      selectlists title_select:        'select#title',\n                  gender_select:       'select#gender',\n                  state_select:        'select#stateProvince'\n      checkbox    :email_opt_in_check, 'input#marketingEmailsOptIn'\n      button      :sign_up_button,     'button#registrationSignUp'\n    end\n```\n\n### Adding Methods to a PageObject\n\nIt is good practice for your Cucumber step definitions to call high level methods in your your `PageObject` instead of\ndirectly accessing and interacting with a page object's UI elements. You can add high level methods to your `PageObject`\nclass definition for interacting with the UI to hide implementation details, as shown below:\n```ruby\n    class LoginPage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'Login' }\n      trait(:page_url)     { '/sign_in' }\n      trait(:page_locator) { 'body.login-body' }\n\n      # Login page UI elements\n      textfield :user_id_field,        'input#userName'\n      textfield :password_field,       'input#password'\n      button    :login_button,         'button#login'\n      checkbox  :remember_checkbox,    'input#rememberUser'\n      label     :error_message_label,  'div#statusBar.login-error'\n      link      :forgot_password_link, 'a.forgotPassword'\n\n      # log in to web app\n      def login(user_id, password)\n        user_id_field.set(user_id)\n        password_field.set(password)\n        login_button.click\n      end\n\n      # set the state of the Remember Me checkbox\n      def remember_me(state)\n        remember_checkbox.set_checkbox_state(state)\n      end\n\n      # verify Login page default UI state\n      def verify_page_ui\n        ui = {\n          self =\u003e { title: 'Login' },\n          login_button =\u003e {\n            visible: true,\n            caption: 'LOGIN'\n          },\n          user_id_field =\u003e {\n            visible: true,\n            enabled: true,\n            value: '',\n            placeholder: 'User name'\n          },\n          password_field =\u003e {\n            visible: true,\n            enabled: true,\n            value: '',\n            placeholder: 'Password'\n          },\n          remember_checkbox =\u003e {\n            exists: true,\n            enabled: true,\n            checked: false\n          },\n          forgot_password_link =\u003e {\n            visible: true,\n            caption: 'Forgot your password?'\n          },\n          error_message_label =\u003e { visible: false }\n        }\n        verify_ui_states(ui)\n      end\n    end\n\n\n    class RegistrationPage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'Registration' }\n      trait(:page_url)     { '/register' }\n      trait(:page_locator) { 'body.registration' }\n\n      # Registration page UI elements\n      textfields  first_name_field:    'input#firstName',\n                  last_name_field:     'input#lastName',\n                  email_field:         'input#email',\n                  phone_number_field:  'input#phone',\n                  address_field:       'input#streetAddress',\n                  city_field:          'input#city',\n                  post_code_field:     'input#postalCode',\n                  password_field:      'input#password',\n                  pword_confirm_field: 'input#passwordConfirmation'\n      selectlists title_select:        'select#title',\n                  gender_select:       'select#gender',\n                  state_select:        'select#stateProvince'\n      checkbox    :email_opt_in_check, 'input#marketingEmailsOptIn'\n      buttons     sign_up_button:      'button#registrationSignUp',\n                  cancel_button:       'button#registrationCancel'\n\n      # populate Registration page fields with profile data\n      def enter_profile_data(profile)\n        fields = { title_select        =\u003e profile.title,\n                   first_name_field    =\u003e profile.first_name,\n                   last_name_field     =\u003e profile.last_name,\n                   gender_select       =\u003e profile.gender,\n                   phone_number_field  =\u003e profile.phone,\n                   email_field         =\u003e profile.email,\n                   address_field       =\u003e profile.address,\n                   city_field          =\u003e profile.city,\n                   state_select        =\u003e profile.state,\n                   post_code_field     =\u003e profile.postal_code,\n                   password_field      =\u003e profile.password,\n                   pword_confirm_field =\u003e profile.confirm_password,\n                   email_opt_in_check  =\u003e profile.email_opt_in\n        }\n        populate_data_fields(fields)\n        sign_up_button.click\n      end\n    end\n```\n\n\nOnce your `PageObjects` have been instantiated, you can call your methods as shown below:\n```ruby\n    login_page.remember_me(true)\n    login_page.login(user_id = 'snicklefritz', password = 'Pa55w0rd')\n```\n\n---\n## PageSections\n\nA `PageSection` is a collection of **UI Elements** that may appear in multiple locations on a page, or on multiple pages\nin a web app. It is a collection of **UI Elements** that represent a conceptual area of functionality, like a navigation\nbar, a search capability, a menu, or a pop-up panel. **UI Elements** and functional behavior are confined to the scope of\na `PageSection` object.\n\nBelow is an example of a header navigation bar feature that is common to multiple pages -\n\n![Navigation Header](https://raw.githubusercontent.com/TestCentricity/testcentricity_web/main/.github/images/NavBar1.png \"Navigation Header\")\n\n -\n\n![Navigation Header](https://raw.githubusercontent.com/TestCentricity/testcentricity_web/main/.github/images/NavBar2.png \"Navigation Header\")\n\nBelow is an example of a popup Shopping Bag panel associated with a header navigation bar -\n\n![Shopping Bag Popup](https://raw.githubusercontent.com/TestCentricity/testcentricity_web/main/.github/images/ShoppingBagPopUp.png \"Shopping Bag Popup\")\n\nA `PageSection` may contain other `PageSection` objects.\n\n\n### Defining a PageSection\n\nYour `PageSection` class definitions should be contained within individual `.rb` files in the `features/support/sections`\nfolder of your test automation project. You define new `PageSection` as shown below:\n```ruby\n    class BagViewPopup \u003c TestCentricity::PageSection\n    end\n```\n\n### Adding Traits to a PageSection\n\nA `PageSection` typically has a root node object that encapsulates a collection of `UIElements`. The `section_locator` trait\nspecifies the CSS or Xpath expression that uniquely identifies that root node object.\n\nYou define your section's **Traits** as shown below:\n```ruby\n    class BagViewPopup \u003c TestCentricity::PageSection\n      trait(:section_locator) { 'aside.ac-gn-bagview' }\n      trait(:section_name)    { 'Shopping Bag Popup' }\n    end\n```\n\n### Adding UI Elements to a PageSection\n\n`PageSections` are typically made up of UI elements like text fields, check boxes, combo boxes, radio buttons, tables, lists,\nbuttons, etc. **UI Elements** are added to your `PageSection` class definition as shown below:\n```ruby\n    class BagViewPopup \u003c TestCentricity::PageSection\n      trait(:section_locator) { 'aside.ac-gn-bagview' }\n      trait(:section_name)    { 'Shopping Bag Popup' }\n\n      # Shopping Bag Popup UI elements\n      label  :bag_message,     'p[class*=\"ac-gn-bagview-message\"]'\n      lists  bag_items_list:   'ul[class*=\"ac-gn-bagview-bag\"]',\n             bag_nav_list:     'ul.ac-gn-bagview-nav-list '\n      button :checkout_button, 'a[class*=\"ac-gn-bagview-button-checkout\"]'\n    end\n```\n\n### Adding Methods to a PageSection\n\nYou can add high level methods to your `PageSection` class definition, as shown below:\n```ruby\n    class BagViewPopup \u003c TestCentricity::PageSection\n      trait(:section_locator) { 'aside.ac-gn-bagview' }\n      trait(:section_name)    { 'Shopping Bag Popup' }\n\n      # Shopping Bag Popup UI elements\n      label  :bag_message,     'p[class*=\"ac-gn-bagview-message\"]'\n      lists  bag_items_list:   'ul[class*=\"ac-gn-bagview-bag\"]',\n             bag_nav_list:     'ul.ac-gn-bagview-nav-list '\n      button :checkout_button, 'a[class*=\"ac-gn-bagview-button-checkout\"]'\n\n      def item_count\n        bag_items_list.visible? ? bag_items_list.item_count : 0\n      end\n\n      def perform_action(action)\n        case action.gsub(/\\s+/, '_').downcase.to_sym\n        when :check_out\n          checkout_button.click\n        when :view_bag\n          bag_nav_list.choose_item(1)\n        when :saved_items\n          bag_nav_list.choose_item(2)\n        when :orders\n          bag_nav_list.choose_item(3)\n        when :account\n          bag_nav_list.choose_item(4)\n        when :sign_in, :sign_out\n          bag_nav_list.choose_item(5)\n        else\n          raise \"#{action} is not a valid selector\"\n        end\n      end\n    end\n```\n\n### Adding PageSections to your PageObject\n\nYou add a `PageSection` to its associated `PageObject` as shown below:\n```ruby\n    class HomePage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'Home' }\n      trait(:page_url)     { '/dashboard' }\n      trait(:page_locator) { 'body.dashboard' }\n\n      # Home page Section Objects\n      section :search_form, SearchForm\n    end\n```\nOnce your `PageObject` has been instantiated, you can call its `PageSection` methods as shown below:\n```ruby\n    home_page.search_form.search_for('ocarina')\n```\n\n---\n## UIElements\n\n`PageObjects` and `PageSections` are typically made up of UI elements like text fields, check boxes, select lists (combo\nboxes), radio buttons, tables, ordered and unordered lists, buttons, images, HTML5 video or audio player objects, etc.\nUI elements are declared and instantiated within the class definition of the `PageObject` or `PageSection` in which they\nare contained. With TestCentricity For Web, all UI elements are based on the `UIElement` class.\n\n\n### Declaring and Instantiating UIElements\n\nSingle `UIElement` declarations have the following format:\n                                     \n    elementType :elementName, locator\n\n* The `elementName` is the unique name that you will use to refer to the UI element and is specified as a `Symbol`.\n* The `locator` is the CSS or XPath attribute that uniquely and unambiguously identifies the `UIElement`.\n\nMultiple `UIElement` declarations for a collection of elements of the same type can be performed by passing a hash table\ncontaining the names and locators of each individual element.\n\n### Example UIElement Declarations\n\nSupported `UIElement` elementTypes and their declarations have the following format:\n\n*Single element declarations:*\n```ruby\n    class SamplePage \u003c TestCentricity::PageObject\n\n      button     :button_name, locator\n      textfield  :field_name, locator\n      checkbox   :checkbox_name, locator\n      radio      :radio_button_name, locator\n      label      :label_name, locator\n      link       :link_name, locator\n      selectlist :select_name, locator\n      list       :list_name, locator\n      table      :table_name, locator\n      range      :range_name, locator\n      image      :image_name, locator\n      video      :video_name, locator\n      audio      :audio_name, locator\n      filefield  :filefield_name, locator\n\n    end\n```\n*Multiple element declarations:*\n```ruby\n    class SamplePage \u003c TestCentricity::PageObject\n\n      buttons     button_1_name: locator,\n                  button_2_name: locator,\n                  button_X_name: locator\n      textfields  field_1_name: locator,\n                  field_2_name: locator,\n                  field_X_name: locator\n      checkboxes  check_1_name: locator,\n                  check_2_name: locator,\n                  check_X_name: locator\n      radios      radio_1_name: locator,\n                  radio_X_name: locator\n      labels      label_1_name: locator,\n                  label_X_name: locator\n      links       link_1_name: locator,\n                  link_X_name: locator\n      selectlists selectlist_1_name: locator,\n                  selectlist_X_name: locator\n      lists       list_1_name: locator,\n                  list_X_name: locator\n      tables      table_1_name: locator,\n                  table_X_name: locator\n      ranges      range_1_name: locator,\n                  range_X_name: locator\n      images      image_1_name: locator,\n                  image_X_name: locator\n      videos      video_1_name: locator,\n                  video_X_name: locator\n      audios      audio_1_name: locator,\n                  audio_X_name: locator\n      filefields  filefield_1_name: locator,\n                  filefield_X_name: locator\n\n    end\n```\n\nRefer to the Class List documentation for the `PageObject` and `PageSection` classes for details on the class methods used\nfor declaring and instantiating `UIElements`. Examples of UI element declarations can be found in the [**section 4.3 (Adding UI Elements to your PageObject)**](#adding-ui-elements-to-a-pageobject)\nand [**section 5.3 (Adding UI Elements to your PageSection)**](#adding-ui-elements-to-a-pagesection) sections above.\n\n\n### UIElement Inherited Methods\n\nWith TestCentricity, all UI elements are based on the `UIElement` class, and inherit the following methods:\n\n**Action methods:**\n\n    element.click\n    element.double_click\n    element.right_click\n    element.click_at(x, y)\n    element.hover\n    element.hover_at(x, y)\n    element.scroll_to(position)\n    element.drag_by(right_offset, down_offset)\n    element.drag_and_drop(target, right_offset, down_offset)\n\n**Object state methods:**\n\n    element.exists?\n    element.visible?\n    element.hidden?\n    element.enabled?\n    element.disabled?\n    element.displayed?\n    element.obscured?\n    element.focused?\n    element.required?\n    element.content_editable?\n    element.crossorigin\n    element.get_value\n    element.count\n    element.style\n    element.title\n    element.width\n    element.height\n    element.x\n    element.y\n    element.get_attribute(attrib)\n    element.get_native_attribute(attrib)\n\n**Waiting methods:**\n\n    element.wait_until_exists(seconds)\n    element.wait_until_gone(seconds)\n    element.wait_until_visible(seconds)\n    element.wait_until_hidden(seconds)\n    element.wait_until_enabled(seconds)\n    element.wait_until_value_is(value, seconds)\n    element.wait_until_value_changes(seconds)\n    element.wait_while_busy(seconds)\n\n**WAI-ARIA Object Accessibility (A11y) methods:**\n\n    element.role\n    element.tabindex\n    element.aria_disabled?\n    element.aria_hidden?\n    element.aria_expanded?\n    element.aria_required?\n    element.aria_invalid?\n    element.aria_checked?\n    element.aria_readonly?\n    element.aria_haspopup?\n    element.aria_selected?\n    element.aria_pressed?\n    element.aria_label\n    element.aria_labelledby\n    element.aria_describedby\n    element.aria_live\n    element.aria_sort\n    element.aria_rowcount\n    element.aria_colcount\n    element.aria_valuemax\n    element.aria_valuemin\n    element.aria_valuenow\n    element.aria_valuetext\n    element.aria_orientation\n    element.aria_roledescription\n    element.aria_autocomplete\n    element.aria_controls\n    element.aria_modal?\n    element.aria_keyshortcuts\n    element.aria_multiline?\n    element.aria_multiselectable?\n    element.aria_busy?\n\n\n### Populating a PageObject or PageSection With Data\n\nA typical automated test may be required to perform the entry of test data by interacting with various `UIElements` on your\n`PageObject` or `PageSection`. This data entry can be performed using the various object action methods (listed above) for\neach `UIElement` that needs to be interacted with.\n\nThe `PageObject.populate_data_fields` and `PageSection.populate_data_fields` methods support the entry of test data into a\ncollection of `UIElements`. The `populate_data_fields` method accepts a hash containing key/hash pairs of `UIElements` and\ntheir associated data to be entered. Data values must be in the form of a `String` for `textfield`, `selectlist`, and `filefield`\ncontrols. For `checkbox` and `radio` controls, data must either be a `Boolean` or a `String` that evaluates to a `Boolean`\nvalue (Yes, No, 1, 0, true, false). For `range` controls, data must be an `Integer`. For `input(type='color')` color picker\ncontrols, which are specified as a `textfield`, data must be in the form of a hex color `String`. For `section` objects,\ndata values must be a `String`, and the `section` object must have a `set` method defined.\n\nThe `populate_data_fields` method verifies that data attributes associated with each `UIElement` is not `nil` or `empty`\nbefore attempting to enter data into the `UIElement`.\n\nThe optional `wait_time` parameter is used to specify the time (in seconds) to wait for each `UIElement` to become viable\nfor data entry (the `UIElement` must be visible and enabled) before entering the associated data value. This option is useful\nin situations where entering data, or setting the state of a `UIElement` might cause other `UIElements` to become visible\nor active. Specifying a wait_time value ensures that the subsequent `UIElements` will be ready to be interacted with as\nstates are changed. If the wait time is `nil`, then the wait time will be 5 seconds.\n```ruby\n    def enter_data(user_data)\n      fields = {\n        first_name_field    =\u003e user_data.first_name,\n        last_name_field     =\u003e user_data.last_name,\n        email_field         =\u003e user_data.email,\n        country_code_select =\u003e user_data.country_code,\n        phone_number_field  =\u003e user_data.phone_number,\n        time_zone_select    =\u003e user_data.time_zone,\n        language_select     =\u003e user_data.language\n      }\n      populate_data_fields(fields, wait_time = 2)\n    end\n```\n\n### Verifying UIElements on a PageObject or PageSection\n\nA typical automated test executes one or more interactions with the user interface, and then performs a validation to verify\nwhether the expected state of the UI has been achieved. This verification can be performed using the various object state\nmethods (listed above) for each `UIElement` that requires verification. Depending on the complexity and number of `UIElements`\nto be verified, the code required to verify the presence of `UIElements` and their correct states can become cumbersome.\n\nThe `PageObject.verify_ui_states` and `PageSection.verify_ui_states` methods support the verification of multiple properties\nof multiple UI elements on a `PageObject` or `PageSection`. The `verify_ui_states` method accepts a hash containing key/hash\npairs of UI elements and their properties or attributes to be verified.\n```ruby\n     ui = {\n       object1 =\u003e { property: state },\n       object2 =\u003e { property: state, property: state },\n       object3 =\u003e { property: state }\n     }\n     verify_ui_states(ui)\n```\nThe `verify_ui_states` method queues up any exceptions that occur while verifying each object's properties until all `UIElements`\nand their properties have been checked, and then posts any exceptions encountered upon completion. Posted exceptions include\na screenshot with a red dashed highlight around the UI element that did not match the expected results.\n\nThe `verify_ui_states` method supports the following property/state pairs:\n\n**All Objects:**\n\n    :exists            Boolean\n    :enabled           Boolean\n    :disabled          Boolean\n    :visible           Boolean\n    :hidden            Boolean\n    :displayed         Boolean\n    :obscured          Boolean\n    :width             Integer\n    :height            Integer\n    :x                 Integer\n    :y                 Integer\n    :class             String\n    :value or :caption String\n    :attribute         Hash\n    :style             String\n    :tabindex          Integer\n    :required          Boolean\n    :crossorigin       String\n\n**Pages:**\n\n    :secure Boolean\n    :title  String\n\n**Text Fields:**\n\n    :readonly    Boolean\n    :placeholder String\n    :maxlength   Integer\n    :min         Integer\n    :max         Integer\n    :step        Integer\n\n  Text Field Constraint Validation\n\n    :validation_message String\n    :badInput           Boolean\n    :customError        Boolean\n    :patternMismatch    Boolean\n    :rangeOverflow      Boolean\n    :rangeUnderflow     Boolean\n    :stepMismatch       Boolean\n    :tooLong            Boolean\n    :tooShort           Boolean\n    :typeMismatch       Boolean\n    :valid              Boolean\n    :valueMissing       Boolean\n\n**Checkboxes:**\n\n    :checked       Boolean\n    :indeterminate Boolean\n\n**Radio Buttons:**\n\n    :selected Boolean\n\n**Links:**\n\n    :href String\n\n**Images**\n\n    :loaded Boolean\n    :broken Boolean\n    :src    String\n    :alt    String\n\n**Lists**\n\n    :items     Array of Strings\n    :itemcount Integer\n    :item      Hash\n    :selected  String\n\n**Select Lists** (ComboBoxes):\n\n    :items or :options         Array of Strings\n    :itemcount or :optioncount Integer\n    :selected                  String\n    :groupcount                Integer\n    :group_headings            Array of Strings\n\n**Tables**\n\n    :rowcount      Integer\n    :columncount   Integer\n    :columnheaders Array of String\n    :cell          Hash\n    :row           Hash\n    :column        Hash\n\n**Audio/Video Media Objects**\n\n    :autoplay              Boolean\n    :ended                 Boolean\n    :controls              Boolean\n    :loop                  Boolean\n    :muted                 Boolean\n    :default_muted         Boolean\n    :paused                Boolean\n    :seeking               Boolean\n    :src                   String\n    :current_time          Float\n    :default_playback_rate Float\n    :duration              Float\n    :playback_rate         Float\n    :ready_state           Integer\n    :volume                Float\n    :preload               String\n    :poster                String\n    :track_count           Integer\n    :active_track          Integer\n    :active_track_data     Hash\n    :all_tracks_data       Array of Hash\n    :track_data            Hash\n    :active_track_source   String\n    :track_source          String\n\n#### ARIA Accessibility Property/State Pairs\n\nThe `verify_ui_states` method supports the following ARIA accessibility property/state pairs:\n\n    :aria_label           String\n    :aria_disabled        Boolean\n    :aria_labelledby      String\n    :aria_describedby     String\n    :aria_live            Boolean\n    :aria_selected        Boolean\n    :aria_hidden          Boolean\n    :aria_expanded        Boolean\n    :aria_required        Boolean\n    :aria_invalid         Boolean\n    :aria_checked         Boolean\n    :aria_readonly        Boolean\n    :aria_pressed         Boolean\n    :aria_busy            Boolean\n    :aria_haspopup        Boolean\n    :aria_sort            String\n    :aria_rowcount        String\n    :aria_colcount        String\n    :aria_valuemax        String\n    :aria_valuemin        String\n    :aria_valuenow        String\n    :aria_valuetext       String\n    :aria_orientation     String\n    :aria_keyshortcuts    String\n    :aria_roledescription String\n    :aria_autocomplete    String\n    :aria_controls        String\n    :aria_modal           String\n    :aria_multiline       Boolean\n    :aria_multiselectable Boolean\n    :content_editable     Boolean\n    :role                 String\n\n#### Comparison States\n\nThe `verify_ui_states` method supports comparison states using property/comparison state pairs:\n```ruby\n    object =\u003e { property: { comparison_state: value } }\n```\nComparison States:\n\n    :lt or :less_than                  Integer or String\n    :lt_eq or :less_than_or_equal      Integer or String\n    :gt or :greater_than               Integer or String\n    :gt_eq or :greater_than_or_equal   Integer or String\n    :starts_with                       String\n    :ends_with                         String\n    :contains                          String\n    :not_contains or :does_not_contain Integer or String\n    :not_equal                         Integer, String, or Boolean\n\nThe example below depicts a `verify_changes_saved` method that uses the `verify_ui_states` method to verify that all expected\nvalues appear in the associated text fields after entering data and performing a save operation.\n```ruby\n    def verify_changes_saved\n      # verify saved user data is correctly displayed\n      ui = {\n        first_name_field =\u003e {\n          visible: true,\n          aria_invalid: false,\n          value: User.current.first_name\n        },\n        last_name_field =\u003e {\n          visible: true,\n          aria_invalid: false,\n          value: User.current.last_name\n        },\n        email_field =\u003e {\n          visible: true,\n          aria_invalid: false,\n          value: User.current.email\n        },\n        phone_number_field =\u003e {\n          visible: true,\n          aria_invalid: false,\n          value: User.current.phone_number\n        },\n        time_zone_select =\u003e {\n          visible: true,\n          aria_invalid: false,\n          value: User.current.time_zone\n        },\n        language_select =\u003e {\n          visible: true,\n          aria_invalid: false,\n          value: User.current.language\n        },\n        avatar_container =\u003e { visible: true },\n        avatar_image =\u003e {\n          visible: true,\n          broken: false,\n          src: { ends_with: User.current.avatar_file_name },\n          alt: \"#{User.current.first_name} #{User.current.last_name}\",\n          style: { contains: 'border-radius: 50%;'}\n        },\n        error_message_label =\u003e { visible: false }\n      }\n      verify_ui_states(ui)\n\n      # verify avatar src url does not contain /null/ in its file path\n      verify_ui_states(avatar_image =\u003e { src: { does_not_contain: \"/null/\" } })\n    end\n```\n\n#### I18n Translation Validation\n\nThe `verify_ui_states` method also supports I18n string translations using property/I18n key name pairs:\n```ruby\n    object =\u003e { property: { translate_key: 'name of key in I18n compatible .yml file' } }\n```\n**I18n Translation Keys:**\n\n    :translate            String\n    :translate_upcase     String\n    :translate_downcase   String\n    :translate_capitalize String\n    :translate_titlecase  String\n\nThe example below depicts the usage of the `verify_ui_states` method to verify that the captions for a popup Shopping Bag\npanel are correctly translated.\n\n![Localized UI](https://raw.githubusercontent.com/TestCentricity/testcentricity_web/main/.github/images/LocalizedUI.png \"Localized UI\")\n```ruby\n    class BagViewPopup \u003c TestCentricity::PageSection\n      trait(:section_locator) { 'aside.ac-gn-bagview' }\n      trait(:section_name)    { 'Shopping Bag Popup' }\n\n      # Shopping Bag Popup UI elements\n      label  :bag_message,     'p[class*=\"ac-gn-bagview-message\"]'\n      lists  bag_items_list:   'ul[class*=\"ac-gn-bagview-bag\"]',\n             bag_nav_list:     'ul.ac-gn-bagview-nav-list '\n      button :checkout_button, 'a[class*=\"ac-gn-bagview-button-checkout\"]'\n\n      def verify_empty_bag_ui\n        nav_items = %w[\n          BagViewPopup.bag\n          BagViewPopup.saved_items\n          BagViewPopup.orders\n          BagViewPopup.account\n          BagViewPopup.sign_in\n        ]\n        ui = {\n          bag_message =\u003e {\n            visible: true,\n            caption: { translate: 'BagViewPopup.bag_is_empty' }\n          },\n          bag_nav_list =\u003e {\n            visible: true,\n            itemcount: 5,\n            items: { translate: nav_items }\n          },\n          bag_items_list =\u003e { visible: false },\n          checkout_button =\u003e { visible: false }\n        }\n        verify_ui_states(ui)\n      end\n    end\n```\nI18n `.yml` files contain key/value pairs representing the name of a translated string (key) and the string value. For the\npopup Shopping Bag panel example above, the translated strings for English, Spanish, and French are represented in below:\n\n**English** - `en.yml`\n```yaml\n    en:\n      BagViewPopup:\n        bag_is_empty: 'Your Bag is empty.'\n        bag: 'Bag'\n        saved_items: 'Saved Items'\n        orders: 'Orders'\n        account: 'Account'\n        sign_in: 'Sign in'\n        sign_out: 'Sign out'\n```\n**Spanish** - `es.yml`\n```yaml\n    es:\n      BagViewPopup:\n        bag_is_empty: 'Tu bolsa está vacía.'\n        bag: 'Bolsa'\n        saved_items: 'Artículos guardados'\n        orders: 'Pedidos'\n        account: 'Cuenta'\n        sign_in: 'Iniciar sesión'\n        sign_out: 'Cerrar sesión'\n```\n**French** - `fr.yml`\n```yaml\n    fr:\n      BagViewPopup:\n        bag_is_empty: 'Votre sac est vide.'\n        bag: 'Sac'\n        saved_items: 'Articles enregistrés'\n        orders: 'Commandes'\n        account: 'Compte'\n        sign_in: 'Ouvrir une session'\n        sign_out: 'Fermer la session'\n```\n\nEach supported language/locale combination has a corresponding `.yml` file. I18n `.yml` file naming convention uses\n[ISO-639 language codes](https://docs.oracle.com/cd/E13214_01/wli/docs92/xref/xqisocodes.html#wp1252447) and\n[ISO-3166 country codes](https://docs.oracle.com/cd/E13214_01/wli/docs92/xref/xqisocodes.html#wp1250799). For example:\n\n| Language (Country)    | File name |\n|-----------------------|-----------|\n| English               | en.yml    |\n| English (Australia)   | en-AU.yml |\n| English (Canada)      | en-CA.yml |\n| French                | fr.yml    |\n| French (Canada)       | fr-CA.yml |\n| Spanish               | es.yml    |\n| German                | de.yml    |\n| Portuguese (Brazil)   | pt-BR.yml |\n| Portuguese (Portugal) | pt-PT.yml |\n\nBaseline translation strings are stored in `.yml` files in the `config/locales/` folder.\n\n       📁 my_automation_project/\n        ├── 📁 config/\n        │   ├── 📁 locales/\n        │   │   ├── 📄 en.yml\n        │   │   ├── 📄 en-AU.yml\n        │   │   ├── 📄 es.yml\n        │   │   ├── 📄 de.yml\n        │   │   ├── 📄 fr.yml\n        │   │   ├── 📄 fr-CA.yml\n        │   │   ├── 📄 pt-BR.yml\n        │   │   └── 📄 pt-PT.yml\n        │   ├── 📁 test_data/\n        │   └── 📄 cucumber.yml\n        ├── 📁 downloads/\n        ├── 📁 features/\n        ├── 📄 Gemfile\n        └── 📄 README.md\n\n\n### Working With Custom UIElements\n\nMany responsive and touch-enabled web based user interfaces are implemented using front-end JavaScript libraries for building\nuser interfaces based on multiple composite UI components. Popular JS libraries include React, Angular, and Ember.js. These\nstylized and adorned controls can present a challenge when attempting to interact with them using Capybara and Selenium based\nautomated tests.\n\n#### Radio and Checkbox UIElements\n\nSometimes, radio buttons and checkboxes implemented using JS component libraries cannot be interacted with due to other UI\nelements being overlaid on top of them, causing the base `input(type='radio')` or `input(type='checkbox')` element to not\nreceive click actions.\n\nIn the screenshot below of an airline flight search and booking page, the **Round-trip**, **One-way**, and **Multi-city**\nradio buttons are overlaid with `div` elements that also acts as proxies for their associated `input(type='radio')` elements,\nand that intercept the `click` actions that would normally be handled by the `input(type='radio')` elements.\n\n![Custom Radio buttons](https://raw.githubusercontent.com/TestCentricity/testcentricity_web/main/.github/images/CustomRadios.png \"Custom Radio buttons\")\n\nThe checkbox controls on the airline flight search and booking page are also overlaid with `div` elements that intercept\nthe `click` actions that would normally be handled by the `input(type='checkbox')` elements.\n\n![Custom Checkbox controls](https://raw.githubusercontent.com/TestCentricity/testcentricity_web/main/.github/images/CustomCheckbox.png \"Custom Checkbox controls\")\n\n\nThe `Radio.define_custom_elements` and `CheckBox.define_custom_elements` methods provide a way to specify the `input`,\n`proxy`, and/or `label` elements associated with the `input(type='radio')` and `input(type='checkbox')` elements. The\n`define_custom_elements` method should be called from an `initialize` method for the `PageObject` or `PageSection` where\nthe `radio` or `checkbox` elements are instantiated.\n\nThe code snippet below demonstrates the use of the `Radio.define_custom_elements` and `CheckBox.define_custom_elements`\nmethods to define the multiple UI elements that comprise each radio button and checkbox.\n```ruby\n    class FlightBookingPage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'Flight Booking Home' }\n      trait(:page_locator) { 'div[class*=\"bookerContainer\"]' }\n\n      # Flight Booking page UI elements\n      radios     roundtrip_radio:  'div[role=\"radiogroup\"] \u003e div.ftRadio:nth-of-type(1)',\n                 one_way_radio:    'div[role=\"radiogroup\"] \u003e div.ftRadio:nth-of-type(2)',\n                 multi_city_radio: 'div[role=\"radiogroup\"] \u003e div.ftRadio:nth-of-type(3)'\n      checkboxes use_miles_check:  'div#divAwardReservation',\n                 flex_dates_check: 'div#divLowFareCalendar \u003e div.left',\n                 near_from_check:  'div#divIncludeNearbyDepartureAirports',\n                 near_to_check:    'div#divIncludeNearbyArrivalAirports'\n\n      def initialize\n        # define the custom element components for the Round Trip, One Way, and Multi-City radio buttons\n        radio_spec = {\n          input: 'input[type=\"radio\"]',\n          label: 'label.normal'\n        }\n        roundtrip_radio.define_custom_elements(radio_spec)\n        one_way_radio.define_custom_elements(radio_spec)\n        multi_city_radio.define_custom_elements(radio_spec)\n\n        # define the custom element components for the checkboxes\n        check_spec = {\n          input: 'input[type=\"checkbox\"]',\n          label: 'label.normal'\n        }\n        use_miles_check.define_custom_elements(check_spec)\n        flex_dates_check.define_custom_elements(check_spec)\n        near_from_check.define_custom_elements(check_spec)\n        near_to_check.define_custom_elements(check_spec)\n      end\n    end\n```\n#### List UIElements\n\nThe basic HTML `list` element is typically composed of the parent `ul` or `ol` object, and one or more `li` elements\nrepresenting the items in the list. However, list controls implemented using JS component libraries can be composed of\nmultiple elements representing the components of a list implementation.\n\nIn the screenshots below, an inspection of the **Menu Groups** horizontal scrolling list on a **Restaurant Detail** page\nreveals that it is a `div` element that contains multiple `button` elements with `data-testid` attributes of `menu-group`\nthat represent the list items that can be selected.\n\n![Custom List](https://raw.githubusercontent.com/TestCentricity/testcentricity_web/main/.github/images/CustomList.png \"Custom List\")\n\nThe `List.define_list_elements` method provides a means of specifying the elements that make up the key components of a\n`list` control. The method accepts a hash of element designators (key) and a CSS or Xpath expression (value) that expression\nthat uniquely identifies the element. Valid element designators are `:list_item`and `:selected_item`.\n\nThe `RestaurantPage` page object's `initialize` method in the code snippet below demonstrates the use of the `List.define_list_elements`\nmethod to define the common components that make up the **Menu Groups** horizontal scrolling list.\n```ruby\n    class RestaurantPage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'Restaurant Detail' }\n      trait(:page_locator) { 'div.restaurant-menus-container' }\n\n      # Restaurant Detail page UI elements\n      list :menu_groups_list, 'div[class*=\"menus-and-groups-selector__SliderItems\"]'\n\n      def initialize\n        super\n        # define the custom list element components for the Menu Groupslists\n        list_spec = { list_item: 'button[data-testid=\"menu-group\"]' }\n        menu_groups_list.define_list_elements(list_spec)\n      end\n    end\n```\n\n#### SelectList UIElements\n\nThe basic HTML `select` element is typically composed of the parent `select` object, and one or more `option` elements\nrepresenting the selectable items in the drop-down list. However, `select` type controls implemented using JS component\nlibraries (React.js, Chosen, GWT, etc.) can be composed of multiple elements representing the various components of a\ndrop-down style `selectlist` implementation.\n\n![Custom SelectList](https://raw.githubusercontent.com/TestCentricity/testcentricity_web/main/.github/images/CustomSelectList1.png \"Custom SelectList\")\n\n\nIn the screenshots below, an inspection of the **Football Teams** selector reveals that it is a `div` element that contains\na `textfield` element (outlined in purple) for inputting a selection by typing, a `ul` element (outlined in blue) that\ncontains the drop-down list, and multiple `li` elements with the `active-result` snippet in their `class` names (outlined\nin orange) that represent the list items or options that can be selected. The currently selected item or option can be\nidentified by an `li` with the `result-selected` snippet in its `class` name. Group headings and items in the drop-down\nlist are represented by `li` elements with a `class` name of `group-result` (outlined in green).\n\n![Custom SelectList](https://raw.githubusercontent.com/TestCentricity/testcentricity_web/main/.github/images/CustomSelectList.jpg \"Custom SelectList\")\n\nThe `SelectList.define_list_elements` method provides a means of specifying the various elements that make up the key\ncomponents of a `selectlist` control. The method accepts a hash of element designators (key) and a CSS or Xpath expression\n(value) that uniquely identifies the element. Valid element designators are `:list_item`, `:options_list`, `:list_trigger`,\n`:selected_item`, `:text_field`, `:group_heading`, and `:group_item`.\n\nThe `CustomControlsPage` page object's `initialize` method in the code snippet below demonstrates the use of the\n`SelectList.define_list_elements` method to define the common components that make up the **Teams** drop-down style selector.\n```ruby\n    class CustomControlsPage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'Custom Controls' }\n      trait(:page_locator) { 'div.custom-controls-page-body' }\n\n      # Custom Controls page UI elements\n      selectlists country_select: 'div#country_chosen',\n                  team_select:    'div#team_chosen'\n\n      def initialize\n        super\n        # define the custom list element components for the Team Chosen selectlists\n        list_spec = {\n          selected_item: 'li[class*=\"result-selected\"]',\n          list_item:     'li[class*=\"active-result\"]',\n          text_field:    'input.chosen-search-input',\n          options_list:  'ul.chosen-results',\n          group_item:    'li.group-result',\n          group_heading: 'li.group-result'\n        }\n        country_select.define_list_elements(list_spec)\n        team_select.define_list_elements(list_spec)\n      end\n    end\n```\n\n#### Table UIElements\n\nThe basic HTML `table` element is typically composed of the parent `table` object, a body (`tbody`) containing one or\nmore rows (`tr`), with each row containing one or more columns (`td`). Tables can also include an optional header (`thead`)\nwith a header row (`tr`) containing one or more header columns (`th`).\n\nHowever, custom tables can be implemented using elements other than the standard table components described above. In the\nscreenshot below, an inspection of the table reveals that it is comprised of `div` elements representing the table, body,\nrows, columns, header, header row, and header columns.\n\n![Custom Table](https://raw.githubusercontent.com/TestCentricity/testcentricity_web/main/.github/images/CustomTable.png \"Custom Table\")\n\nThe `Table.define_table_elements` method provides a means of specifying the various elements that make up the key\ncomponents of a `table`. The method accepts a hash of element designators (key) and a CSS or Xpath expression (value)\nthat uniquely identifies the element. Valid element designators are `:table_header`, `:header_row`, `:header_column`,\n`:table_body`, `:table_row`, and `:table_column`.\n\nThe `CustomControlsPage` page object's `initialize` method in the code snippet below demonstrates the use of the\n`Table.define_table_elements` method to define the components that make up the responsive `table`.\n```ruby\n    class CustomControlsPage \u003c TestCentricity::PageObject\n      trait(:page_name)    { 'Custom Controls' }\n      trait(:page_locator) { 'div.custom-controls-page-body' }\n\n      # Custom Controls page UI elements\n      table :custom_table, 'div#resp-table'\n\n      def initialize\n        super\n        # define the custom element components for the table\n        table_spec = {\n          table_header:  'div.resp-table-header',\n          header_row:    'div.resp-table-row',\n          header_column: 'div.table-header-cell',\n          table_body:    'div.resp-table-body',\n          table_row:     'div.resp-table-row',\n          table_column:  'div.table-body-cell'\n        }\n        custom_table.define_table_elements(table_spec)\n      end\n    end\n```\n\n---\n## Instantiating Your PageObjects\n\nBefore you can call the methods in your `PageObjects` and `PageSections`, you must instantiate the `PageObjects` of your\nweb application, as well as create instance variables which can be used when calling a `PageObject`'s methods from your\nstep definitions. There are several ways to instantiate your `PageObjects`.\n\nOne common implementation is shown below:\n```ruby\n    module WorldPages\n      def login_page\n        @login_page ||= LoginPage.new\n      end\n\n      def home_page\n        @home_page ||= HomePage.new\n      end\n\n      def registration_page\n        @registration_page ||= RegistrationPage.new\n      end\n\n      def search_results_page\n        @search_results_page ||= SearchResultsPage.new\n      end\n    end\n\n    World(WorldPages)\n```\nThe `WorldPages` module above can be defined in your `env.rb` file, or you can define it in a separate `world_pages.rb`\nfile in the `features/support` folder.\n\nWhile this approach is effective for small web applications with only a few pages (and hence few `PageObjects`), it quickly\nbecomes cumbersome to manage if your web application has dozens of `PageObjects` that need to be instantiated and managed.\n\n### Using the PageManager\n\nThe `PageManager` class provides methods for supporting the instantiation and management of `PageObjects`. In the code\nexample below, the `page_objects` method contains a hash table of your `PageObject` instances and their associated\n`PageObject` classes to be instantiated by `PageManager`:\n```ruby\n    module WorldPages\n      def page_objects\n        {\n          login_page:                LoginPage,\n          home_page:                 HomePage,\n          registration_page:         RegistrationPage,\n          search_results_page:       SearchResultsPage,\n          products_grid_page:        ProductsCollectionPage,\n          product_detail_page:       ProductDetailPage,\n          shopping_basket_page:      ShoppingBasketPage,\n          payment_method_page:       PaymentMethodPage,\n          confirm_purchase_page:     PurchaseConfirmationPage,\n          my_account_page:           MyAccountPage,\n          my_order_history_page:     MyOrderHistoryPage,\n          my_ship_to_addresses_page: MyShipToAddressesPage,\n          terms_conditions_page:     TermsConditionsPage,\n          privacy_policy_page:       PrivacyPolicyPage,\n          faqs_page:                 FAQsPage,\n          contact_us_page:           ContactUsPage\n        }\n      end\n    end\n\n    World(WorldPages)\n```\n    \nThe `WorldPages` module above should be defined in the `world_pages.rb` file in the `features/support` folder.\n\nInclude the code below in your `env.rb` file to ensure that your `PageObjects` are instantiated before your Cucumber\nscenarios are executed:\n```ruby\n    include WorldPages\n    WorldPages.instantiate_page_objects\n```\n**NOTE:** If you intend to use the `PageManager`, you must define a `page_name` trait for each of the `PageObjects` to\nbe registered.\n\n\n### Leveraging the PageManager in Your Cucumber Tests\n\nMany Cucumber based automated tests suites include scenarios that verify that web pages are correctly loaded, displayed,\nor can be navigated to by clicking associated links. One such Cucumber navigation scenario is displayed below:\n```gherkin\n    Scenario Outline:  Verify Home page navigation links\n      Given I am on the Home page\n      When I click the \u003cpage name\u003e navigation link\n      Then I expect the \u003cpage name\u003e page to be correctly displayed\n\n      Examples:\n        |page name          |\n        |Registration       |\n        |My Account         |\n        |Terms \u0026 Conditions |\n        |Privacy Policy     |\n        |FAQs               |\n        |Contact Us         |\n```\nIn the above example, the step definitions associated with the 3 steps might be implemented using a `page_dispatcher`\nmethod using a `case` statement to parse the `page` parameter as in the example below:\n```ruby\n    Given(/^I am on the (.*) page$/) do |page_name|\n      target_page = page_dispatcher(page_name)\n      target_page.load_page\n    end\n\n    When(/^I click the (.*) navigation link$/) do |link_name|\n      target_page = page_dispatcher(link_name)\n      target_page.navigate_to\n    end\n\n    Then(/^I expect the (.*) page to be correctly displayed$/) do |page_name|\n      target_page = page_dispatcher(page_name)\n      target_page.verify_page_exists\n      target_page.verify_page_ui\n    end\n\n    # this method takes a page name as a parameter and returns an instance of the associated Page Object\n      def page_dispatcher(page_name)\n        page = case page_name\n               when 'Registration'\n                 registration_page\n               when 'My Account'\n                 my_account_page\n               when 'Terms \u0026 Conditions'\n                 terms_conditions_page\n               when 'Privacy Policy'\n                 privacy_policy_page\n               when 'Contact Us'\n                 contact_us_page\n               when 'FAQs'\n                 faqs_page\n               end\n        raise \"No page object defined for page named '#{page_name}'\" unless page\n        page\n      end\n````\n\nWhile this approach may be effective for small web applications with only a few pages (and hence few `PageObjects`), it\nquickly becomes cumbersome to manage if your web application has dozens of `PageObjects` that need to be managed.\n\nThe `PageManager` class provides a `find_page` method that replaces the cumbersome and difficult to maintain `case`\nstatement used in the above example. The `PageManager.current_page` method allows you to set or get an instance of the\ncurrently active Page Object.\n\nTo use these `PageManager` methods, include the step definitions and code below in a `page_steps.rb` or `generic_steps.rb`\nfile in the `features/step_definitions` folder:\n```ruby\n    include TestCentricity\n\n    Given(/^I am on the (.*) page$/) do |page_name|\n      target_page = PageManager.find_page(page_name)\n      target_page.load_page\n    end\n\n    When(/^I click the (.*) navigation link$/) do |page_name|\n      target_page = PageManager.find_page(page_name)\n      target_page.navigate_to\n    end\n\n    Then(/^I expect to see the (.*) page$/) do |page_name|\n      target_page = PageManager.find_page(page_name)\n      target_page.verify_page_exists\n    end\n\n    Then(/^I expect the (.*) page to be correctly displayed$/) do |page_name|\n      target_page = PageManager.find_page(page_name)\n      target_page.verify_page_exists\n      target_page.verify_page_ui\n    end\n```\n\n---\n## Connecting to Web Browsers\n\nSince its inception, TestCentricity has provided support for establishing a single connection to a target desktop or mobile\nweb browser by instantiating a WebDriver object. **Environment Variables** are used to specify the local, grid, or remote\ncloud hosted target web browser, and the various WebDriver capability parameters required to configure the driver object.\nThe appropriate **Environment Variables** are typically specified in the command line at runtime through the use of profiles\nset in a `cucumber.yml` file (Refer to [**section 8.9 (Using Browser Specific Profiles in `cucumber.yml`)**](#using-browser-specific-profiles-in-cucumber-yml) below).\n\nHowever, for those use cases requiring the instantiation of multiple WebDriver objects within a test case or test scenario,\n**Environment Variables** are a less effective means of specifying multiple driver capabilities. And even in those use cases\nwhere only a single WebDriver object is required, there are a growing number of optional Selenium and Appium capabilities\nthat are being offered by cloud hosted browser service providers (like BrowserStack, Sauce Labs, TestingBot, or LambdaTest)\nthat **Environment Variables** may not effectively address.\n\nBeginning with TestCentricity version 4.4.0, the `TestCentricity::WebDriverConnect.initialize_web_driver` method accepts\nan optional `options` hash for specifying desired capabilities (using the W3C protocol), driver type, driver name, endpoint\nURL, device type, and desktop web browser window size information. TestCentricity also now supports the instantiation of\nmultiple WebDriver objects to establish connections with, and coordinate test execution between multiple desktop and/or\nmobile web browser instances.\n\nSome use cases for the verification of real-time multiple user interactions across multiple concurrent browsers or devices are:\n  - Chat, Messaging, or Social Media apps/web portals used by one or more users interacting in real time (posts, reposts, likes)\n  - Ride Hailing/Sharing Services with separate Rider and Driver experience apps/web portals\n  - Food Delivery Services with a Customer app for finding restaurants and ordering food, a Restaurant app for fulfilling\n    the food order and coordinating delivery, and a Driver app for ensuring delivery of the order to the customer\n  - Learning Management/Student Engagement platforms that allow teachers to monitor student engagement and progress on assigned\n    activities and support for remote real-time collaboration between students and teachers\n\nIf the optional `options` hash is not provided when calling the `TestCentricity::WebDriverConnect.initialize_web_driver` method,\nthen **Environment Variables** must be used to specify the target local or remote web browser, and the various webdriver\ncapability parameters required to establish a connection with a single target web browser.\n\n### Specifying Options and Capabilities in the `options` Hash\n\nFor those test scenarios requiring the instantiation of multiple WebDriver objects, or where cumbersome **Environment\nVariables** are less than ideal, call the `TestCentricity::WebDriverConnect.initialize_web_driver` method with an `options`\nhash that specifies the WebDriver desired capabilities and the driver type, as depicted in the example below:\n```ruby\n    options = {\n      capabilities: { browserName: :firefox },\n      driver: :webdriver\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\nAdditional options that can be specified in an `options` hash include the following:\n\n| Option          | Purpose                                                                                    |\n|-----------------|--------------------------------------------------------------------------------------------|\n| `browser_size:` | optional desktop web browser window size (width and height)                                |\n| `driver_name:`  | optional driver name                                                                       |\n| `endpoint:`     | optional endpoint URL for remote grid or cloud hosted browser service providers            |\n| `device_type:`  | only used for locally or cloud hosted mobile device browser - set to `:phone` or `:tablet` |\n\nDetails on specifying desired capabilities, driver type, endpoint URL, and default driver names are provided in each of the\nbrowser hosting sections below.\n\n#### Specifying the Driver Type\n\nThe `driver:` type is a required entry in the `options` hash when instantiating a WebDriver object using the `initialize_web_driver`\nmethod. Valid `driver:` type values are listed in the table below:\n\n| `driver:`       | **Driver Type**                                                                            |\n|-----------------|--------------------------------------------------------------------------------------------|\n| `:webdriver`    | locally hosted desktop or emulated mobile browser                                          |\n| `:grid`         | Selenium Grid 4 hosted browser                                                             |\n| `:appium`       | locally hosted native iOS/Android mobile browser using device simulator or physical device |\n| `:browserstack` | remote browser hosted on BrowserStack                                                      |\n| `:saucelabs`    | remote browser hosted on Sauce Labs                                                        |\n| `:testingbot`   | remote browser hosted on TestingBot                                                        |\n| `:lambdatest`   | remote browser hosted on LambdaTest                                                        |\n| `:custom`       | remote browser hosted on unsupported cloud based browser hosting services                  |\n\n#### Specifying a Driver Name\n\nAn optional user defined `driver_name:` can be specified in the `options` hash when instantiating a WebDriver object using\nthe `TestCentricity::WebDriverConnect.initialize_web_driver` method. If a driver name is not specified, the `initialize_web_driver`\nmethod will assign a default driver name comprised of the specified driver type (`driver:`) and the `browserName:` specified\nin the `capabilities:` hash. Details on default driver names are provided in each of the browser hosting sections below.\n\nFor those test scenarios requiring the instantiation of multiple WebDriver objects, each driver object should be assigned a\nunique driver name, which is used when switching between driver contexts. For instance, when performing end-to-end testing\nof a Food Delivery Service which consists of separate web portals for the Customer Experience (find, order, and pay for food),\nthe Restaurant Experience (menu management, order fulfillment, and order delivery dispatch), and the Delivery Driver Experience\n(customer location and tracking), 3 driver objects must be instantiated.\n\nAssigning meaningful unique driver names for the 3 driver objects (`:customer_portal`, `:merchant_portal`, `:delivery_portal`)\nin the `options` hash when calling `TestCentricity::WebDriverConnect.initialize_web_driver` method reduces confusion when\nswitching between the driver objects using the `TestCentricity:WebDriverConnect.activate_driver(driver_name)` method, which\nexpects a driver name, specified as a `Symbol`.\n\n### Setting Desktop Browser Window Size\n\n#### Using `:browser_size` in the `options` Hash\n\nThe size (width and height) of a desktop browser window can be specified in the `options` hash for browsers that are hosted\nlocally, in a Selenium Grid, or by a cloud hosted browser service provider. You cannot set the size of a mobile device web\nbrowser, which is determined by the mobile device's screen size.\n\nTo set the size of a desktop browser window in the `options` hash, you specify a `:browser_size` with the desired width and\nheight in pixels as shown below:\n```ruby\n    options = {\n      browser_size: [1100, 900],\n      capabilities: { browserName: :edge },\n      driver: :webdriver\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\nTo maximize a desktop browser window, you specify a `:browser_size` of 'max' as shown below:\n```ruby\n    options = {\n      browser_size: 'max',\n      capabilities: { browserName: :chrome },\n      driver: :webdriver\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\nIf a `:browser_size` is not specified, then the default size of a desktop browser window will be set to the size specified\nin the `BROWSER_SIZE` Environment Variable (if it has been specified) or to a default width and height of 1650 by 1000 pixels.\n\n#### Using the `BROWSER_SIZE` Environment Variable\n\nTo set the size of a desktop browser window without using an `options` hash, you set the `BROWSER_SIZE` Environment Variable\nto the desired width and height in pixels as shown below:\n\n    BROWSER_SIZE=1600,1000\n\nTo maximize a desktop browser window, you set the `BROWSER_SIZE` Environment Variable to 'max' as shown below:\n\n    BROWSER_SIZE=max\n\nIf the `BROWSER_SIZE` Environment Variable is not specified, then the default size of a desktop browser window will be set\nto a width and height of 1650 by 1000 pixels.\n\n\n### Locally Hosted Desktop Web Browsers\n\nFor locally hosted desktop web browsers running on macOS, Windows, or Linux platforms, the browser type and driver type\nmust be specified when calling the `TestCentricity::WebDriverConnect.initialize_web_driver` method. The table below contains\nthe values that can be used to specify the locally hosted desktop web browser to be instantiated when calling the\n`initialize_web_driver` method:\n\n| `browserName:` or `WEB_BROWSER` | **Desktop Platform**                                |\n|---------------------------------|-----------------------------------------------------|\n| `chrome`                        | macOS, Windows, or Linux                            |\n| `chrome_headless`               | macOS, Windows, or Linux (headless - no visible UI) |\n| `firefox`                       | macOS, Windows, or Linux                            |\n| `firefox_headless`              | macOS, Windows, or Linux (headless - no visible UI) |\n| `edge`                          | macOS or Windows                                    |\n| `edge_headless`                 | macOS or Windows (headless - no visible UI)         |\n| `safari`                        | macOS only                                          |\n\n#### Local Desktop Browser using Environment Variables\n\nIf the `options` hash is not provided when calling the `TestCentricity::WebDriverConnect.initialize_web_driver` method, then\nthe following Environment Variables must be set as described in the table below:\n\n| **Environment Variable** | **Description**                                                   |\n|--------------------------|-------------------------------------------------------------------|\n| `WEB_BROWSER`            | Must be set to one of the values from the table above             |\n| `DRIVER`                 | Must be set to `webdriver`                                        |\n| `BROWSER_SIZE`           | [Optional] Set to _'width in pixels, heigh in pixels'_ or _'max'_ |\n\nRefer to [**section 8.9 (Using Browser Specific Profiles in `cucumber.yml`)**](#using-browser-specific-profiles-in-cucumber-yml) below.\n\n\n#### Local Desktop Browser in the `options` Hash\n\nWhen using the `options` hash, the following options and capabilities must be specified:\n- `driver:` must be set to `:webdriver`\n- `browserName:` in the `capabilities:` hash must be set to one of the values from the table above\n\n```ruby\n    options = {\n      capabilities: { browserName: value_from_table_above },\n      driver: :webdriver\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\n\u003e ℹ️ If an optional user defined `driver_name:` is not specified in the `options` hash, the default driver name will be set to\n`:local_\u003cbrowserName\u003e` - e.g. `:local_chrome` or `:local_edge_headless`.\n\nBelow is an example of an `options` hash for specifying a connection to a locally hosted Firefox desktop web browser. The\n`options` hash includes options for specifying the driver name and setting the browser window size.\n```ruby\n    options = {\n      driver: :webdriver,\n      driver_name: :customer_context,\n      browser_size: [1400, 1100],\n      capabilities: { browserName: :firefox }\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\n\n#### Testing File Downloads With Desktop Browsers\n\nFile download functionality can be tested with locally hosted instances of Chrome, Edge, or Firefox desktop browsers. Your\nautomation project must set the `DOWNLOADS` Environment Variable to `true`, which will result in a `/downloads` folder being\ncreated, which will be used as the destination for files that are downloaded by your automated tests. The `/downloads` folder\nwill be at the same level as the `/config` and `/features` folders, as depicted below:\n\n        📁 my_automation_project/\n        ├── 📁 config/\n        ├── 📁 downloads/\n        ├── 📁 features/\n        ├── 📄 Gemfile\n        └── 📄 README.md\n\n\nWhen running tests in multiple concurrent threads using the `parallel_tests` gem, a new folder will be created within the\n`/downloads` folder for each test thread. This is to ensure that files downloaded in each test thread are isolated from tests\nrunning in other parallel threads. An example of the`/downloads` folder structure for 4 parallel threads is depicted below:\n\n        📁 my_automation_project/\n        ├── 📁 config/\n        ├── 📁 downloads/\n        │   ├── 📁 1/\n        │   ├── 📁 2/\n        │   ├── 📁 3/\n        │   └── 📁 4/\n        ├── 📁 features/\n        ├── 📄 Gemfile\n        └── 📄 README.md\n\n\nWhen testing file downloads using a local instance of Firefox, you will need to specify the MIME types of the various file\ntypes that your tests will be downloading. This is accomplished by setting the `MIME_TYPES` Environment Variable to a\ncomma-delimited string containing the list of MIME types to be accepted. The `MIME_TYPES` Environment Variable should be\nset before initializing the Firefox web driver. This list of file types is required as it will prevent Firefox from displaying\nthe File Download modal dialog, which will halt your automated tests. An example of a list of MIME types is depicted below:\n```ruby\n    # set list of all supported MIME types for testing file downloads with Firefox\n    mime_types = [\n      'application/pdf',\n      'image/png',\n      'image/jpeg',\n      'image/gif',\n      'text/csv',\n      'text/plain'\n    ]\n    ENV['MIME_TYPES'] = mime_types.join(',')\n```\n\nA detailed list of file MIME types can be found [here](https://www.freeformatter.com/mime-types-list.html).\n\n\n### Locally Hosted Emulated Mobile Web Browsers\n\nYou can run your tests against mobile device browsers that are emulated within a locally hosted instance of a Chrome desktop\nbrowser on macOS or Windows. The specified mobile browser's user agent, CSS screen dimensions, and default screen orientation\nwill be automatically set within the local Chrome browser instance. You may also specify the emulated device's screen orientation.\n\n\u003e ⚠️ For best results when testing against mobile web browsers, you should run your tests against iOS and Android simulators\nor physical devices, either hosted locally or via a remotely cloud hosted service.\n\nFor locally hosted emulated mobile web browsers, the `WEB_BROWSER` Environment Variable must be set to one of the values\nfrom the table below:\n\n| `browserName:` or `WEB_BROWSER` | **CSS Screen Dimensions** | **Default Orientation** | **OS Version**       |\n|---------------------------------|---------------------------|-------------------------|----------------------|\n| `iphone_11`                     | 414 x 896                 | portrait                | iOS 15.5             |\n| `iphone_11_pro`                 | 375 x 812                 | portrait                | iOS 15.5             |\n| `iphone_11_pro_max`             | 414 x 896                 | portrait                | iOS 15.5             |\n| `iphone_12_mini`                | 375 x 812                 | portrait                | iOS 15.5             |\n| `iphone_12`                     | 390 x 844                 | portrait                | iOS 15.5             |\n| `iphone_12_pro`                 | 390 x 844                 | portrait                | iOS 15.5             |\n| `iphone_12_pro_max`             | 428 x 926                 | portrait                | iOS 15.5             |\n| `iphone_13_mini`                | 375 x 812                 | portrait                | iOS 15.5             |\n| `iphone_13`                     | 390 x 844                 | portrait                | iOS 15.5             |\n| `iphone_13_pro`                 | 390 x 844                 | portrait                | iOS 15.5             |\n| `iphone_13_pro_max`             | 428 x 926                 | portrait                | iOS 15.5             |\n| `iphone_se`                     | 375 x 667                 | portrait                | iOS 15.5             |\n| `iphone_14`                     | 390 x 844                 | portrait                | iOS 16.2             |\n| `iphone_14_plus`                | 428 x 926                 | portrait                | iOS 16.2             |\n| `iphone_14_pro`                 | 393 x 852                 | portrait                | iOS 16.2             |\n| `iphone_14_pro_max`             | 430 x 932                 | portrait                | iOS 16.2             |\n| `ipad`                          | 1080 x 810                | landscape               | iOS 15.5             |\n| `ipad_mini`                     | 1133 x 744                | landscape               | iOS 15.5             |\n| `ipad_air`                      | 1180 x 820                | landscape               | iOS 15.5             |\n| `ipad_pro_11`                   | 1194 x 834                | landscape               | iOS 15.5             |\n| `ipad_pro_12_9`                 | 1366 x 1024               | landscape               | iOS 15.5             |\n| `pixel_5`                       | 393 x 851                 | portrait                | Android 12           |\n| `pixel_6`                       | 412 x 915                 | portrait                | Android 12           |\n| `pixel_xl`                      | 412 x 732                 | portrait                | Android 12           |\n| `nexus_10`                      | 1280 x 800                | landscape               | Android 12           |\n| `pixel_c`                       | 1280 x 900                | landscape               | Android 12           |\n| `kindle_fire`                   | 1024 x 600                | landscape               |                      |\n| `kindle_firehd7`                | 800 x 480                 | landscape               | Fire OS 3            |\n| `kindle_firehd8`                | 1280 x 800                | landscape               | Fire OS 5            |\n| `kindle_firehd10`               | 1920 x 1200               | landscape               | Fire OS 5            |\n| `surface`                       | 1366 x 768                | landscape               |                      |\n| `blackberry_playbook`           | 1024 x 600                | landscape               | BlackBerry Tablet OS |\n| `windows_phone7`                | 320 x 480                 | portrait                | Windows Phone OS 7.5 |\n| `windows_phone8`                | 320 x 480                 | portrait                | Windows Phone OS 8.0 |\n| `lumia_950_xl`                  | 360 x 640                 | portrait                | Windows Phone OS 10  |\n| `blackberry_z10`                | 384 x 640                 | portrait                | BlackBerry 10 OS     |\n| `blackberry_z30`                | 360 x 640                 | portrait                | BlackBerry 10 OS     |\n| `blackberry_leap`               | 360 x 640                 | portrait                | BlackBerry 10 OS     |\n| `blackberry_passport`           | 504 x 504                 | square                  | BlackBerry 10 OS     |\n\n#### Local Emulated Mobile Browser using Environment Variables\n\nIf the `options` hash is not provided when calling the `TestCentricity::WebDriverConnect.initialize_web_driver` method,\nthen the following Environment Variables must be set as described in the table below:\n\n| **Environment Variable** | **Description**                                       |\n|--------------------------|-------------------------------------------------------|\n| `WEB_BROWSER`            | Must be set to one of the values from the table above |\n| `DRIVER`                 | Must be set to `webdriver`                            |\n| `ORIENTATION`            | [Optional] Set to `portrait` or `landscape`           |\n\nRefer to [**section 8.9 (Using Browser Specific Profiles in `cucumber.yml`)**](#using-browser-specific-profiles-in-cucumber-yml) below.\n\n\n#### Local Emulated Mobile Browser in the `options` Hash\n\nWhen using the `options` hash, the following options and capabilities must be specified:\n- `driver:` must be set to `:webdriver`\n- `browserName:` in the `capabilities:` hash must be set to one of the values from the table above\n\n```ruby\n    options = {\n      capabilities: { browserName: value_from_table_above },\n      driver: :webdriver\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\nTo change the emulated device's screen orientation from the default setting, set the optional `orientation:` to either\n`:portrait` or `:landscape` in the `capabilities:` hash as shown in the example below:\n```ruby\n    options = {\n      capabilities: {\n        browserName: :ipad_pro_12_9,\n        orientation: :portrait\n      },\n      driver: :webdriver\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\n\u003e ℹ️ If an optional user defined `driver_name:` is not specified in the `options` hash, the default driver name will be set to\n`:local_\u003cbrowserName\u003e` - e.g. `:local_ipad_pro_12_9` or `:local_pixel_6`.\n\nBelow is an example of an `options` hash for specifying a connection to a locally hosted emulated mobile Safari web browser\nrunning on an iPhone. The`options` hash includes options for specifying the driver name and setting the browser orientation\nto landscape mode.\n```ruby\n    options = {\n      driver: :webdriver,\n      driver_name: :user1,\n      capabilities: {\n        browserName: :iphone_13_pro_max,\n        orientation: :landscape\n      }\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\n\n#### User Defined Emulated Mobile Browser Profiles\n\nUser defined mobile browser profiles can be specified in a `device.yml` file for testing locally hosted emulated mobile\nweb browsers running in an instance of the Chrome desktop browser. The user specified browser profiles must be located\nat `config/data/devices/devices.yml` as depicted below:\n\n        📁 my_automation_project/\n        ├── 📁 config/\n        │   ├── 📁 data/\n        │   │   └── 📁 devices/\n        │   │       └── 📄devices.yml\n        │   ├── 📁 locales/\n        │   ├── 📁 test_data/\n        │   └── 📄 cucumber.yml\n        ├── 📁 downloads/\n        ├── 📁 features/\n        ├── 📄 Gemfile\n        └── 📄 README.md\n\nThe format for a new mobile browser profile is:\n```yaml\n    :my_device_profile:\n      :name: \"My New Device Name\"\n      :os: (ios, android, kindle, or blackberry)\n      :type: (phone or tablet)\n      :css_width: css width in pixels\n      :css_height: css height in pixels\n      :default_orientation: (portrait or landscape)\n      :user_agent: \"user agent string\"\n```\n\nTo specify a user defined emulated mobile browser, set `browserName:` or the `WEB_BROWSER` Environment Variable to the\ndevice's profile name.\n\n\n### Selenium Grid Hosted Desktop and Emulated Mobile Web Browsers\n\nFor remotely hosted desktop web browsers running on a Selenium 4 Grid, the browser type and driver type must be specified\nwhen calling the `TestCentricity::WebDriverConnect.initialize_web_driver` method. The table below contains the values that\ncan be used to specify the grid hosted desktop web browser to be instantiated when calling the`initialize_web_driver` method:\n\n| `browserName:` or `WEB_BROWSER` |\n|---------------------------------|\n| `chrome`                        |\n| `chrome_headless`               |\n| `firefox`                       |\n| `firefox_headless`              |\n| `edge`                          |\n| `edge_headless`                 |\n\n#### Grid Browsers using Environment Variables\n\nIf the `options` hash is not provided when calling the `TestCentricity::WebDriverConnect.initialize_web_driver` method,\nthen the following Environment Variables must be set as described in the table below:\n\n| **Environment Variable** | **Description**                                                                                                                   |\n|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------|\n| `WEB_BROWSER`            | Must be set to one of the values from the table above, or any of the emulated mobile web browsers described above in section 8.4. |\n| `DRIVER`                 | Must be set to `grid`                                                                                                             |\n| `REMOTE_ENDPOINT`        | [Optional] Set to the URL of the Grid hub. Set to `http://localhost:4444/wd/hub` if not specified                                 |\n| `BROWSER_SIZE`           | [Optional] Set to _'width in pixels, heigh in pixels'_ or _'max'_                                                                 |\n\nRefer to [**section 8.9 (Using Browser Specific Profiles in `cucumber.yml`)**](#using-browser-specific-profiles-in-cucumber-yml) below.\n\n\n#### Grid Browser in the `options` Hash\n\nWhen using the `options` hash, the following options and capabilities must be specified:\n- `driver:` must be set to `:grid`\n- `browserName:` in the `capabilities:` hash must be set to one of the values from the table above\n\n```ruby\n    options = {\n      capabilities: { browserName: value_from_table_above },\n      driver: :grid,\n      endpoint: 'http://localhost:4444/wd/hub'\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\n\u003eℹ️ If an optional user defined `driver_name:` is not specified in the `options` hash, the default driver name will be set to\n`:remote_\u003cbrowserName\u003e` - e.g. `:remote_chrome` or `:remote_edge_headless`.\n\u003e \n\u003e ℹ️ If an `endpoint:` is not specified in the `options`hash, then the default remote endpoint URL of `http://localhost:4444/wd/hub`\nwill be used.\n\nBelow is an example of an `options` hash for specifying a connection to a grid hosted Chrome desktop web browser. The\n`options` hash includes options for specifying the driver name and setting the browser window size.\n```ruby\n    options = {\n      driver: :grid,\n      driver_name: :admin_user,\n      browser_size: [1400, 1100],\n      capabilities: { browserName: :chrome }\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\n\n### Locally Hosted Mobile Browsers on Simulators or Physical Devices\n\nRefer to [this page](https://appium.io/docs/en/2.2/guides/caps/) for information regarding specifying Appium capabilities.\n\n⚠️ If you are running locally hosted mobile web tests on iOS or Android simulators or devices using version 1.x of the Appium\nserver, the `APPIUM_SERVER_VERSION` environment variable must be set to `1` in order to ensure that the correct Appium server\nendpoint is used.\n\n#### Mobile Safari Browser on iOS Simulators or iOS Physical Devices\n\nYou can run your mobile web tests against the mobile Safari browser on iOS device simulators or physically connected iOS\ndevices using Appium and XCode on macOS. You must install Appium, XCode, and the iOS version-specific device simulators\nfor XCode. Information about Appium setup and configuration requirements with the XCUITest driver for testing on physically\nconnected iOS devices can be found on [this page](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/real-device-config.md). Refer to [this page](https://appium.github.io/appium-xcuitest-driver/5.12/capabilities/) for information regarding specifying\nAppium capabilities that are specific to the XCUITest driver.\n\nThe Appium server must be running prior to invoking Cucumber to run your features/scenarios. Refer to [**section 8.6.3 (Starting and Stopping Appium Server)**](#starting-and-stopping-appium-server) below.\n\n\n##### Local Mobile Safari Browser using Environment Variables\n\nIf the `options` hash is not provided when calling the `TestCentricity::WebDriverConnect.initialize_web_driver` method,\nthe following **Environment Variables** must be set as described in the table below.\n\n| **Environment Variable**   | **Description**                                                                                                                                                       |\n|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `DRIVER`                   | Must be set to `appium`                                                                                                                                               |\n| `AUTOMATION_ENGINE`        | Must be set to `xcuitest`                                                                                                                                             |\n| `APP_PLATFORM_NAME`        | Must be set to `iOS`                                                                                                                                                  |\n| `APP_BROWSER`              | Must be set to `Safari`                                                                                                                                               |\n| `APP_VERSION`              | Must be set to which ever iOS version you wish to run within the XCode Simulator                                                                                      |\n| `APP_DEVICE`               | Set to iOS device name supported by the iOS Simulator (`iPhone 13 Pro Max`, `iPad Pro (12.9-inch) (5th generation)`, etc.) or name of physically connected iOS device |\n| `DEVICE_TYPE`              | Must be set to `phone` or `tablet`                                                                                                                                    |\n| `UDID`                     | UDID of physically connected iOS device (not used for simulators)                                                                                                     |\n| `TEAM_ID`                  | unique 10-character Apple developer team identifier string (not used for simulators)                                                                                  |\n| `TEAM_NAME`                | String representing a signing certificate (not used for simulators)                                                                                                   |\n| `APP_ALLOW_POPUPS`         | [Optional] Allow javascript to open new windows in Safari. Set to `true` or `false`                                                                                   |\n| `APP_IGNORE_FRAUD_WARNING` | [Optional] Prevent Safari from showing a fraudulent website warning. Set to `true` or `false`                                                                         |\n| `APP_NO_RESET`             | [Optional] Don't reset app state after each test. Set to `true` or `false`                                                                                            |\n| `APP_FULL_RESET`           | [Optional] Perform a complete reset. Set to `true` or `false`                                                                                                         |\n| `APP_INITIAL_URL`          | [Optional] Initial URL, default is a local welcome page.  e.g.  `http://www.apple.com`                                                                                |\n| `WDA_LOCAL_PORT`           | [Optional] Used to forward traffic from Mac host to real iOS devices over USB. Default value is same as port number used by WDA on device.                            |\n| `ORIENTATION`              | [Optional] Set to `portrait` or `landscape` (only for iOS simulators)                                                                                                 |\n| `NEW_COMMAND_TIMEOUT`      | [Optional] Time (in Seconds) that Appium will wait for a new command from the client                                                                                  |\n| `SHOW_SIM_KEYBOARD`        | [Optional] Show the simulator keyboard during text entry. Set to `true` or `false`                                                                                    |\n| `SHUTDOWN_OTHER_SIMS`      | [Optional] Close any other running simulators. Set to `true` or `false`. See note below.                                                                              |\n\nThe `SHUTDOWN_OTHER_SIMS` environment variable can only be set if you are running Appium Server with the `--relaxed-security`\nor `--allow-insecure=shutdown_other_sims` arguments passed when starting it from the command line, or when running the server\nfrom the Appium Server GUI app. A security violation error will occur without relaxed security enabled. \n\nRefer to [**section 8.9 (Using Browser Specific Profiles in `cucumber.yml`)**](#using-browser-specific-profiles-in-cucumber-yml) below.\n\n\n##### Local Mobile Safari Browser in the `options` Hash\n\nWhen using the `options` hash, the following options and capabilities must be specified:\n- `driver:` must be set to `:appium`\n- `device_type:` must be set to `:tablet` or `:phone`\n- `browserName:` must be set to 'Safari' in the `capabilities:` hash\n- `platformName:` must be set to 'ios' in the `capabilities:` hash\n- `'appium:automationName':` must be set to 'xcuitest' in the `capabilities:` hash\n- `'appium:platformVersion':` must be set to the version of iOS on the simulator or physical device\n- `'appium:deviceName':` must be set to the name of the iOS simulator or physical device\n\n```ruby\n    options = {\n      driver: :appium,\n      device_type: phone_or_tablet,\n      capabilities: {\n        platformName: 'ios',\n        browserName: 'Safari',\n        'appium:automationName': 'xcuitest',\n        'appium:platformVersion': ios_version,\n        'appium:deviceName': device_or_simulator_name\n      },\n      endpoint: 'http://localhost:4723'\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\n\u003e ℹ️ If an optional user defined `driver_name:` is not specified in the `options` hash, the default driver name will be set to\n`appium_safari`.\n\u003e \n\u003e ℹ️ If an `endpoint:` is not specified in the `options` hash, then the default remote endpoint URL of `http://localhost:4723`\nwill be used.\n\nBelow is an example of an `options` hash for specifying a connection to a locally hosted mobile Safari web browser running\non an iPad simulator. The `options` hash includes options for specifying the driver name and setting the simulated device\norientation to portrait mode.\n```ruby\n      options = {\n        driver: :appium,\n        device_type: :tablet,\n        driver_name: :student_ipad,\n        capabilities: {\n          platformName: 'ios',\n          browserName: 'Safari',\n          'appium:platformVersion': '15.4',\n          'appium:deviceName': 'iPad Pro (12.9-inch) (5th generation)',\n          'appium:automationName': 'xcuitest',\n          'appium:orientation': 'PORTRAIT'\n        }\n      }\n      WebDriverConnect.initialize_web_driver(options)\n```\n\n#### Mobile Chrome or Android Browsers on Android Studio Virtual Device Emulators\n\nYou can run your mobile web tests against the mobile Chrome or Android browser on emulated Android devices using Appium and\nAndroid Studio on macOS. You must install Android Studio, the desired Android version-specific virtual device emulators, and\nAppium. Refer to [this page](https://appium.io/docs/en/2.2/quickstart/uiauto2-driver/) for information on configuring Appium to work with the Android SDK. Refer to [this page](https://github.com/appium/appium-uiautomator2-driver)\nfor information regarding specifying  Appium capabilities that are specific to the UiAutomator2 driver.\n\nThe Appium server must be running prior to invoking Cucumber to run your features/scenarios. Refer to [**section 8.6.3 (Starting and Stopping Appium Server)**](#starting-and-stopping-appium-server) below.\n\n\n##### Local Mobile Android Browsers using Environment Variables\n\nIf the `options` hash is not provided when calling the `TestCentricity::WebDriverConnect.initialize_web_driver` method,\nthe following **Environment Variables** must be set as described in the table below.\n\n| **Environment Variable**  | **Description**                                                                                                                |\n|---------------------------|--------------------------------------------------------------------------------------------------------------------------------|\n| `DRIVER`                  | Must be set to `appium`                                                                                                        |\n| `AUTOMATION_ENGINE`       | Must be set to `UiAutomator2`                                                                                                  |\n| `APP_PLATFORM_NAME`       | Must be set to `Android`                                                                                                       |\n| `APP_BROWSER`             | Must be set to `Chrome` or `Browser`                                                                                           |\n| `APP_VERSION`             | Must be set to which ever Android OS version you wish to run with the Android Virtual Device                                   |\n| `APP_DEVICE`              | Set to Android Virtual Device ID (`Pixel_2_XL_API_26`, `Nexus_6_API_23`, etc.) found in Advanced Settings of AVD Configuration |\n| `DEVICE_TYPE`             | Must be set to `phone` or `tablet`                                                                                             |\n| `ORIENTATION`             | [Optional] Set to `portrait` or `landscape`                                                                                    |\n| `APP_INITIAL_URL`         | [Optional] Initial URL, default is a local welcome page.  e.g.  `http://www.apple.com`                                         |\n| `APP_NO_RESET`            | [Optional] Don't reset app state after each test. Set to `true` or `false`                                                     |\n| `APP_FULL_RESET`          | [Optional] Perform a complete reset. Set to `true` or `false`                                                                  |\n| `NEW_COMMAND_TIMEOUT`     | [Optional] Time (in Seconds) that Appium will wait for a new command from the client                                           |\n| `CHROMEDRIVER_EXECUTABLE` | [Optional] Absolute local path to ChromeDriver executable                                                                      |\n\nRefer to [**section 8.9 (Using Browser Specific Profiles in `cucumber.yml`)**](#using-browser-specific-profiles-in-cucumber-yml) below.\n\n\n##### Local Mobile Android Browser in the `options` Hash\n\nWhen using the `options` hash, the following options and capabilities must be specified:\n- `driver:` must be set to `:appium`\n- `device_type:` must be set to `:tablet` or `:phone`\n- `browserName:` must be set to 'Chrome' in the `capabilities:` hash\n- `platformName:` must be set to 'Android' in the `capabilities:` hash\n- `'appium:automationName':` must be set to 'UiAutomator2' in the `capabilities:` hash\n- `'appium:platformVersion':` must be set to the version of Android on the simulator or physical device\n- `'appium:deviceName':` must be set to the Android Virtual Device ID\n\n```ruby\n    options = {\n      driver: :appium,\n      device_type: phone_or_tablet,\n      capabilities: {\n        platformName: 'Android',\n        browserName: 'Chrome',\n        'appium:automationName': 'UiAutomator2',\n        'appium:platformVersion': android_version,\n        'appium:deviceName': simulator_name,\n        'appium:avd': simulator_name\n      },\n      endpoint: 'http://localhost:4723'\n    }\n    WebDriverConnect.initialize_web_driver(options)\n```\n\u003e ℹ️ If an optional user defined `driver_name:` is not specified in the `options` hash, the default driver name will be set to\n`appium_chrome`.\n\u003e \n\u003e ℹ️ If an `endpoint:` is not specified in the `options` hash, then the default remote endpoint URL of `http://localhost:4723`\nwill be used.\n\nBelow is an example of an `options` hash for specifying a connection to a locally hosted mobile Chrome web browser running\non an Android phone simulator. The `options` hash includes options for specifying the driver name, setting the simulated\ndevice orientation to landscape mode, and specifying the path to the ChromeDriver executable.\n```ruby\n      options = {\n        driver: :appium,\n        device_type: :phone,\n        driver_name: :student_phone,\n        capabilities: {\n          platformName: 'Android',\n          browserName: 'Chrome',\n          'appium:platformVersion': '12.0',\n          'appium:deviceName': 'Pixel_5_API_31',\n          'appium:avd': 'Pixel_5_API_31',\n          'appium:automationName': 'UiAutomator2',\n          'appium:orientation': 'LANDSCAPE',\n          'appium:chromedriverExecutable': '/Users/Shared/config/webdrivers/chromedriver'\n        }\n      }\n      WebDriverConnect.initialize_web_driver(options)\n```\n\n#### Starting and Stopping Appium Server\n\n##### Using Appium Server with Cucumber\n\nThe Appium server must be running prior to invoking Cucumber to run your features/scenarios on locally hosted mobile","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftestcentricity%2Ftestcentricity_web","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftestcentricity%2Ftestcentricity_web","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftestcentricity%2Ftestcentricity_web/lists"}