diff --git a/CHANGELOG.md b/CHANGELOG.md index fd57fff10..54189d745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Magento Functional Testing Framework Changelog ================================================ +2.5.1 +----- + +### Fixes +* Fixed missing `use` statement in the generate:suite command + +### GitHub Issues +* [#471](https://github.com/magento/magento2-functional-testing-framework/issues/471) -- PHP Fatal error: MftfApplicationConfig not found in GenerateSuiteCommand + 2.5.0 ----- * Traceability diff --git a/bin/mftf b/bin/mftf index 7f9db3524..b978d99ee 100755 --- a/bin/mftf +++ b/bin/mftf @@ -29,7 +29,7 @@ try { try { $application = new Symfony\Component\Console\Application(); $application->setName('Magento Functional Testing Framework CLI'); - $application->setVersion('2.5.0'); + $application->setVersion('2.5.1'); /** @var \Magento\FunctionalTestingFramework\Console\CommandListInterface $commandList */ $commandList = new \Magento\FunctionalTestingFramework\Console\CommandList; foreach ($commandList->getCommands() as $command) { diff --git a/composer.json b/composer.json index e7ee72ff1..f980005c1 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento2-functional-testing-framework", "description": "Magento2 Functional Testing Framework", "type": "library", - "version": "2.5.0", + "version": "2.5.1", "license": "AGPL-3.0", "keywords": ["magento", "automation", "functional", "testing"], "config": { diff --git a/composer.lock b/composer.lock index 28cbd1d4e..275ae9778 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "79e0a6006597df1c5511869876dd7777", + "content-hash": "beb8473a3c21b83da864289149fc03b4", "packages": [ { "name": "allure-framework/allure-codeception", diff --git a/docs/best-practices.md b/docs/best-practices.md index 180f815ca..a3e5f2629 100644 --- a/docs/best-practices.md +++ b/docs/best-practices.md @@ -84,19 +84,19 @@ Use the _Foo.camelCase_ naming convention, which is similar to _Classes_ and _cl Use an upper case first letter for: -- File names. Example: _StorefrontCreateCustomerTest.xml_ -- Test name attributes. Example: ``. -- Data entity names. Example: ``. -- Page name. Example: ``. -- Section name. Example: `
`. -- Action group name. Example: ``. +- File names. Example: _StorefrontCreateCustomerTest.xml_ +- Test name attributes. Example: ``. +- Data entity names. Example: ``. +- Page name. Example: ``. +- Section name. Example: `
`. +- Action group name. Example: ``. #### Lower case Use a lower case first letter for: -- Data keys. Example: ``. -- Element names. Examples: ``. +- Data keys. Example: ``. +- Element names. Examples: ``. ## Page object @@ -134,9 +134,9 @@ Define these three elements and reference them by name in the tests. 1. Keep your tests short and granular for target testing, easier reviews, and easier merge conflict resolution. It also helps you to identify the cause of test failure. 1. Use comments to keep tests readable and maintainable: - - Keep the inline `` and [``] tags up to date. + - Keep the inline `` and [``] tags up to date. It helps to inform the reader of what you are testing and to yield a more descriptive Allure report. - - Explain in comments unclear or tricky test steps. + - Explain in comments unclear or tricky test steps. 1. Refer to [sections] instead of writing selectors. ## Test step merging order diff --git a/docs/credentials.md b/docs/credentials.md index 4b1e2cc2d..3065454b0 100644 --- a/docs/credentials.md +++ b/docs/credentials.md @@ -148,8 +148,8 @@ Credentials can be used in actions: [`fillField`][], [`magentoCLI`][], and [`cre Define the value as a reference to the corresponding key in the credentials file or vault such as `{{_CREDS.my_data_key}}`: -- `_CREDS` is an environment constant pointing to the `.credentials` file -- `my_data_key` is a key in the the `.credentials` file or vault that contains the value to be used in a test step +- `_CREDS` is an environment constant pointing to the `.credentials` file +- `my_data_key` is a key in the the `.credentials` file or vault that contains the value to be used in a test step For example, reference secret data in the [`fillField`][] action with the `userInput` attribute. @@ -180,5 +180,5 @@ The MFTF tests delivered with Magento application do not use credentials and do [Download Vault]: https://www.hashicorp.com/products/vault/ [Login Vault]: https://www.vaultproject.io/docs/commands/login.html [Vault KV2]: https://www.vaultproject.io/docs/secrets/kv/kv-v2.html -[`CREDENTIAL_VAULT_ADDRESS`]: configuration.md#CREDENTIAL_VAULT_ADDRESS -[`CREDENTIAL_VAULT_SECRET_BASE_PATH`]: configuration.md#CREDENTIAL_VAULT_SECRET_BASE_PATH +[`CREDENTIAL_VAULT_ADDRESS`]: configuration.md#credential_vault_address +[`CREDENTIAL_VAULT_SECRET_BASE_PATH`]: configuration.md#credential_vault_secret_base_path diff --git a/docs/data.md b/docs/data.md index 4c618ae45..2a1b845d4 100644 --- a/docs/data.md +++ b/docs/data.md @@ -17,8 +17,8 @@ userInput="{{SimpleSubCategory.name}}" In this example: -* `SimpleSubCategory` is an entity name. -* `name` is a `` key of the entity. The corresponding value will be assigned to `userInput` as a result. +* `SimpleSubCategory` is an entity name. +* `name` is a `` key of the entity. The corresponding value will be assigned to `userInput` as a result. ### Environmental data @@ -28,8 +28,8 @@ userInput="{{_ENV.MAGENTO_ADMIN_USERNAME}}" In this example: -* `_ENV` is a reference to the `dev/tests/acceptance/.env` file, where basic environment variables are set. -* `MAGENTO_ADMIN_USERNAME` is a name of an environment variable. +* `_ENV` is a reference to the `dev/tests/acceptance/.env` file, where basic environment variables are set. +* `MAGENTO_ADMIN_USERNAME` is a name of an environment variable. The corresponding value will be assigned to `userInput` as a result. ### Sensitive data @@ -40,10 +40,10 @@ userInput="{{_CREDS.my_secret_token}}" In this example: -* `_CREDS` is a constant to reference to the `dev/tests/acceptance/.credentials` file, where sensitive data and secrets are stored for use in a test. -* `MY_SECRET_TOKEN` is the name of a key in the credentials variable. +* `_CREDS` is a constant to reference to the `dev/tests/acceptance/.credentials` file, where sensitive data and secrets are stored for use in a test. +* `MY_SECRET_TOKEN` is the name of a key in the credentials variable. The corresponding value of the credential will be assigned to `userInput` as a result. -* The decrypted values are only available in the `.credentials` file in which they are stored. +* The decrypted values are only available in the `.credentials` file in which they are stored. Learn more in [Credentials][]. @@ -59,8 +59,8 @@ userInput="$createCustomer.email$" In this example: -* `createCustomer` is a step key of the corresponding test step that creates an entity. -* `email` is a data key of the entity. +* `createCustomer` is a step key of the corresponding test step that creates an entity. +* `email` is a data key of the entity. The corresponding value will be assigned to `userInput` as a result.
@@ -118,10 +118,10 @@ The format of `` is: The following conventions apply to MFTF ``: -* A `` file may contain multiple data entities. -* Camel case is used for `` elements. The name represents the `` type. For example, a file with customer data is `CustomerData.xml`. A file for simple product would be `SimpleProductData.xml`. -* Camel case is used for the entity name. -* The file name must have the suffix `Data.xml`. +* A `` file may contain multiple data entities. +* Camel case is used for `` elements. The name represents the `` type. For example, a file with customer data is `CustomerData.xml`. A file for simple product would be `SimpleProductData.xml`. +* Camel case is used for the entity name. +* The file name must have the suffix `Data.xml`. ## Example @@ -152,16 +152,16 @@ All entities that have the same name will be merged during test generation. Both `_defaultCategory` sets three data fields: -* `name` defines the category name as `simpleCategory` with a unique suffix. Example: `simpleCategory598742365`. -* `name_lwr` defines the category name in lowercase format with a unique suffix. Example: `simplecategory697543215`. -* `is_active` sets the enable category to `true`. +* `name` defines the category name as `simpleCategory` with a unique suffix. Example: `simpleCategory598742365`. +* `name_lwr` defines the category name in lowercase format with a unique suffix. Example: `simplecategory697543215`. +* `is_active` sets the enable category to `true`. `SimpleSubCategory` sets four data fields: -* `name` that defines the category name with a unique suffix. Example: `SimpleSubCategory458712365`. -* `name_lwr` that defines the category name in lowercase format with a unique suffix. Example: `simplesubcategory753698741`. -* `is_active` sets the enable category to `true`. -* `include_in_menu` that sets the include in the menu to `true`. +* `name` that defines the category name with a unique suffix. Example: `SimpleSubCategory458712365`. +* `name_lwr` that defines the category name in lowercase format with a unique suffix. Example: `simplesubcategory753698741`. +* `is_active` sets the enable category to `true`. +* `include_in_menu` that sets the include in the menu to `true`. The following is an example of a call in test: diff --git a/docs/debugging.md b/docs/debugging.md index 4b839ff6e..be17e952a 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -2,15 +2,15 @@ Debugging within the Magento Functional Testing Framework is helpful in identifying test bugs by allowing you to pause execution so that you may: -- Examine the page. -- Check returned data and other variables being used during run-time. +- Examine the page. +- Check returned data and other variables being used during run-time. This is straightforward to do once you create a basic Debug Configuration. ## Prerequisites -- [Xdebug][] -- PHPUnit configured for use in [PHPStorm][] +- [Xdebug][] +- PHPUnit configured for use in [PHPStorm][] ## Creating Debug Configuration with PHPStorm @@ -27,8 +27,8 @@ If you get a warning `Path to Codeception for local machine is not configured.`: The easiest method of tagging a test for debugging is the following: -- In your Debug configuration, locate `Test Runner options:` and set `--group testDebug`. -- When you want to debug a test you are working on, simply add `` to the annotations. Be sure to remove this after done debugging. +- In your Debug configuration, locate `Test Runner options:` and set `--group testDebug`. +- When you want to debug a test you are working on, simply add `` to the annotations. Be sure to remove this after done debugging. Your Debug Configuration should now be able to run your test and pause execution on any breakpoints you have set in the generated `.php` file under the `_generated` folder. diff --git a/docs/extending.md b/docs/extending.md index 666121986..91dc97c5d 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -5,9 +5,9 @@ For example, only one or two parameters (for example, URL) might vary between te To avoid copy-pasting and to save some time the Magento Functional Testing Framework (MFTF) enables you to extend test components such as [test], [data], and [action group]. You can create or update any component of the parent body in your new test/action group/entity. -* A test starting with `` creates a test `SampleTest` that takes body of existing test `ParentTest` and adds to it the body of `SampleTest`. -* An action group starting with `` creates an action group based on the `ParentActionGroup`, but with the changes specified in `SampleActionGroup`. -* An entity starting with `` creates an entity `SampleEntity` that is equivalent to merging the `SampleEntity` with the `ParentEntity`. +* A test starting with `` creates a test `SampleTest` that takes body of existing test `ParentTest` and adds to it the body of `SampleTest`. +* An action group starting with `` creates an action group based on the `ParentActionGroup`, but with the changes specified in `SampleActionGroup`. +* An entity starting with `` creates an entity `SampleEntity` that is equivalent to merging the `SampleEntity` with the `ParentEntity`. Specify needed variations for a parent object and produce a copy of the original that incorporates the specified changes (the "delta"). @@ -71,8 +71,8 @@ __Use case__: Create two similar tests with different `url` (`"{{AdminCategoryPa __Use case__: Create two similar tests where the second test contains two additional steps: -* `checkOption` before `click` (`stepKey="clickLogin"`) -* `seeInCurrentUrl` after `click` in the `LogInAsAdminTest` test (in the `.../Backend/Test/LogInAsAdminTest.xml` file) +* `checkOption` before `click` (`stepKey="clickLogin"`) +* `seeInCurrentUrl` after `click` in the `LogInAsAdminTest` test (in the `.../Backend/Test/LogInAsAdminTest.xml` file) > Tests with "extends": @@ -119,8 +119,8 @@ __Use case__: Create two similar tests where the second test contains two additi __Use case__: Create two similar tests where the second one contains two additional actions in the `before` hook: -* `checkOption` before `click` (`stepKey="clickLogin"`) -* `seeInCurrentUrl` after `click` in the `LogInAsAdminTest` test (in the `.../Backend/Test/LogInAsAdminTest.xml` file) +* `checkOption` before `click` (`stepKey="clickLogin"`) +* `seeInCurrentUrl` after `click` in the `LogInAsAdminTest` test (in the `.../Backend/Test/LogInAsAdminTest.xml` file) > Tests with "extends": @@ -366,4 +366,4 @@ __Use case__: Create an entity named `DivPanelGreen`, which is similar to the `D [test]: ./test.md [data]: ./data.md [action group]: ./test/action-groups.md -[actions]: ./test/actions.md \ No newline at end of file +[actions]: ./test/actions.md diff --git a/docs/getting-started.md b/docs/getting-started.md index 450dbc2a6..626cca31b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -10,10 +10,10 @@ The latest Magento 2.2 release supports MFTF 2.3.8. Make sure that you have the following software installed and configured on your development environment: -- [PHP version supported by the Magento instance under test][php] -- [Composer 1.3 or later][composer] -- [Java 1.8 or later][java] -- [Selenium Server Standalone 3.1 or later][selenium server] and [ChromeDriver 2.33 or later][chrome driver] or other webdriver in the same directory +- [PHP version supported by the Magento instance under test][php] +- [Composer 1.3 or later][composer] +- [Java 1.8 or later][java] +- [Selenium Server Standalone 3.1 or later][selenium server] and [ChromeDriver 2.33 or later][chrome driver] or other webdriver in the same directory
[PhpStorm] supports [Codeception test execution][], which is helpful when debugging. @@ -147,16 +147,16 @@ vim dev/tests/acceptance/.env Specify the following parameters, which are required to launch tests: -- `MAGENTO_BASE_URL` must contain a domain name of the Magento instance that will be tested. +- `MAGENTO_BASE_URL` must contain a domain name of the Magento instance that will be tested. Example: `MAGENTO_BASE_URL=http://magento.test` -- `MAGENTO_BACKEND_NAME` must contain the relative path for the Admin area. +- `MAGENTO_BACKEND_NAME` must contain the relative path for the Admin area. Example: `MAGENTO_BACKEND_NAME=admin` -- `MAGENTO_ADMIN_USERNAME` must contain the username required for authorization in the Admin area. +- `MAGENTO_ADMIN_USERNAME` must contain the username required for authorization in the Admin area. Example: `MAGENTO_ADMIN_USERNAME=admin` -- `MAGENTO_ADMIN_PASSWORD` must contain the user password required for authorization in the Admin area. +- `MAGENTO_ADMIN_PASSWORD` must contain the user password required for authorization in the Admin area. Example: `MAGENTO_ADMIN_PASSWORD=123123q`
@@ -222,8 +222,8 @@ During testing, the MFTF generates test reports in CLI. You can generate visual representations of the report data using [Allure Framework][]. To view the reports in GUI: -- [Install Allure][] -- Run the tool to serve the artifacts in `dev/tests/acceptance/tests/_output/allure-results/`: +- [Install Allure][] +- Run the tool to serve the artifacts in `dev/tests/acceptance/tests/_output/allure-results/`: ```bash allure serve dev/tests/acceptance/tests/_output/allure-results/ diff --git a/docs/guides/selectors.md b/docs/guides/selectors.md new file mode 100644 index 000000000..3e019bb75 --- /dev/null +++ b/docs/guides/selectors.md @@ -0,0 +1,344 @@ +# How To write good selectors + +Selectors are the atomic unit of test writing. They fit into the hierarchy like this: MFTF tests make use of action groups > which are made up of actions > which interact with page objects > which contain elements > which are specified by selectors. Because they are fundamental building blocks, we must take care when writing them. + +## What is a selector? + +A "selector" works like an address to an element in the Document Object Model (DOM). It specifies page elements and allows MFTF to interact with them. +By 'element' we mean things such as input fields, buttons, tables, divs, etc. +By 'interact' we mean actions such as click, fill field, etc. + +Selectors live inside of MFTF page objects and are meant to be highly re-usable amongst all tests. They can be written in either CSS or XPath. + +## Why are good selectors important? + +Good selectors are important because they are the most re-used component of functional testing. They are the lowest building blocks of tests; the foundation. If they are unstable then everything else built on top of them will inherit that instability. + +## How do I write good selectors? + +We could cover this subject with an infinite amount of documentation and some lessons only come from experience. This guide explains some DOs and DONTs to help you along the way towards selector mastery. + +### Inspecting the DOM + +To write a selector you need to be able to see the DOM and find the element within it. Fortunately you do not have to look at the entire DOM every time. Nor do you have to read it from top to bottom. Instead you can make use of your browsers built-in developer tools or go a step further and try out some popular browser extensions. + +See these links for more information about built-in browser developer tools: + +* [Chrome Developer Tools](https://developers.google.com/web/tools/chrome-devtools/) +* [Firefox Developer Tools](https://developer.mozilla.org/en-US/docs/Tools) + +See these links for common browser addons that may offer advantages over browser developer tools: + +* [Live editor for CSS, Less & Sass - Magic CSS](https://chrome.google.com/webstore/detail/live-editor-for-css-less/ifhikkcafabcgolfjegfcgloomalapol?hl=en) +* [XPath Helper](https://chrome.google.com/webstore/detail/xpath-helper/hgimnogjllphhhkhlmebbmlgjoejdpjl?hl=en) + +### CSS vs XPath + +There are similarities and differences between CSS and XPath. Both are powerful and complex in ways that are outside of the scope of this document. +In general: + +* CSS is more stable, easier to read, and easier to maintain (typically). +* XPath provides several powerful tools and it has been around the longest so it is well documented. +* XPath can be less stable and potentially unsupported by certain actions in Selenium. + +### Priority + +The best and most simple selector will always be to use an element ID: `#some-id-here`. If only we were so lucky to have this every time. + +When writing selectors, you should prioritize finding in this order: + +1. ID, name, class, or anything else that is unique to the element +2. Complex CSS selectors +3. XPath selectors +4. If none of the above work for you, then the last resort is to ask a developer to add a unique ID or class to the element you are trying to select. + +We suggest the use of CSS selectors above XPath selectors when possible. + +### Writing proper selectors + +There are correct ways of writing selectors and incorrect ways. These suggestions will help you write better selectors. + +#### Incorrect - copy selector/xpath + +DO NOT right click on an element in your browser developer tools and select "Copy selector" or "Copy XPath" and simply use that as your selector. These auto-generated selectors are prime examples of what not to do. + +These are bad: + +```css +#html-body > section > div > div > div > div +``` + +```xpath +//*[@id='html-body']/section/div/div/div/div +``` + +Both include unnecessary hierarchical details. As written, we are looking for a `div` inside of a `div` inside of a `div` inside of... you get the picture. If an application developer adds another `div` parent tomorrow, for whatever reason, this selector will break. Furthermore, when reading it, it is not clear what the intended target is. It may also grab other elements that were not intended. + +#### Do not be too general + +DO NOT make your selectors too generic. If a selector is too generic, there is a high probability that it will match multiple elements on the page. Maybe not today, but perhaps tomorrow when the application being tested changes. + +These are bad: + +```html +input[name*='firstname'] +``` + +The `*=` means `contains`. The selector is saying 'find an input whose name contains the string "firstname"'. But if a future change adds a new element to the page whose name also contains "firstname", then this selector will match two elements and that is bad. + +```css +.add +``` + +Similarly here, this will match all elements which contains the class `.add`. This is brittle and susceptible to breaking when new elements/styles are added to the page. + +#### Avoid being too specific + +DO NOT make your selectors too specific either. If a selector is too specific, there is a high probability that it will break due to even minor changes to the application being tested. + +These are bad: + +```css +#container .dashboard-advanced-reports .dashboard-advanced-reports-description .dashboard-advanced-reports-title +``` + +This selector is too brittle. It would break very easily if an application developer does something as simple as adding a parent container for style reasons. + +```xpath +//*[@id='container']/*[@class='dashboard-advanced-reports']/*[@class='dashboard-advanced-reports-description']/*[@class='dashboard-advanced-reports-title'] +``` + +This is the same selector as above, but represented in XPath instead of CSS. It is brittle for the same reasons. + +#### XPath selectors should not use @attribute="foo" + +This XPath is fragile. It would fail if the attribute was `attribute="foo bar"`. Instead you should use `contains(@attribute, "foo")` where @attribute is any valid attribute such as @text or @class. + +#### CSS and XPath selectors should avoid making use of hardcoded indices + +Hardcoded values are by definition not flexible. A hardcoded index may change if new code is introduced. Instead, parameterize the selector. + +GOOD: .foo:nth-of-type({{index}}) + +BAD: .foo:nth-of-type(1) + +GOOD: button[contains(@id, "foo")][{{index}}] + +BAD: button[contains(@id, "foo")][1] + +GOOD: #actions__{{index}}__aggregator + +BAD: #actions__1__aggregator + +#### CSS and XPath selectors MUST NOT reference the @data-bind attribute + +The @data-bind attribute is used by KnockoutJS, a framework Magento uses to create dynamic Javascript pages. Since this @data-bind attribute is tied to a specific framework, it should not be used for selectors. If Magento decides to use a different framework then these @data-bind selectors would break. + +#### Use isolation + +You should think in terms of "isolation" when writing new selectors. + +For example, say you have a login form that contains a username field, a password field, and a 'Sign In' button. First isolate the parent element. Perhaps it's `#login-form`. Then target the child element under that parent element: `.sign-in-button` The result is `#login-form .sign-in-button`. + +Using isolation techniques reduces the amount of DOM that needs to be processed. This makes the selector both accurate and efficient. + +#### Use advanced notation + +If you need to interact with the parent element but it is too generic, and the internal contents are unique then you need to: + +1. Target the unique internal contents first. +1. Then jump to the parent element using `::parent`. + +Imagine you want to find a table row that contains the string "Jerry Seinfeld". You can use the following XPath selector: + +```xpath +//div[contains(text(), 'Jerry Seinfeld')]/parent::td/parent::tr +``` + +Note in this instance that CSS does not have an equivalent to `::parent`, so XPath is a better choice. + +### CSS Examples + +Examples of common HTML elements and the corresponding selector to find that element in the DOM: + +Type|HTML|Selector +---|---|--- +IDs|`
`|`#idname` +Classes|`
`|`.classname` +HTML Tags|`
`|`div` +HTML Tag & ID|`
`|`div#idname` +HTML Tag & Class|`
`|`div.classname` +ID & Class|`
`|`#idname.classname` +HTML Tag & ID & Class|`
`|`div#idname.classname` + +Examples of common CSS selector operators and their purpose: + +Symbol|Name|Purpose|Selector +---|---|---|--- +`*`|Universal Selector|Allows you to select ALL ELEMENTS on the Page. Wild Card.|`*` +Whitespace|Descendant Combinator|Allows you to combine 2 or more selectors.|`#idname .classname` +`>`|Child Combinator|Allows you to select the top-level elements THAT FOLLOWS another specified element.|`#idname > .classname` +`+`|Adjacent Sibling Combinator|Allows you to select an element THAT FOLLOWS DIRECTLY AFTER another specified element.|`#idname + .classname` +`~`|General Sibling Combinator|Allows you to select an element THAT FOLLOWS (directly or indirectly) another specified element.|`#idname ~ .classname` + +Examples of CSS attribute operators and their purpose: + +Symbol|Purpose|Example +---|---|--- +`=`|Returns all elements that CONTAIN the EXACT string in the value.|`[attribute='value']` +`*=`|Returns all elements that CONTAINS the substring in the value.|`[attribute*='value']` +`~=`|Returns all elements that CONTAINS the given words delimited by spaces in the value.|`[attribute~='value']` +`$=`|Returns all elements that ENDS WITH the substring in the value.|`[attribute$='value']` +`^=`|Returns all elements that BEGIN EXACTLY WITH the substring in the value.|`[attribute^='value']` +`!=`|Returns all elements that either DOES NOT HAVE the given attribute or the value of the attribute is NOT EQUAL to the value.|`[attribute!='value']` + +### XPath Examples + +#### `/` vs `//` + +The absolute XPath selector is a single forward slash `/`. It is used to provide a direct path to the element from the root element. + +WARNING: The `/` selector is brittle and should be used sparingly. + +Here is an example of what NOT to do, but this demonstrates how the selector works: + +```xpath +/html/body/div[2]/div/div[2]/div[1]/div[2]/form/div/input +``` + +In the BAD example above, we are specifying a very precise path to an input element in the DOM, starting from the very top of the document. + +Similarly, the relative XPath selector is a double forward slash `//`. It is used to start searching for an element anywhere in the DOM. + +Example: + +```xpath +//div[@class=’form-group’]//input[@id='user-message'] +``` + +#### Parent Selectors + +The parent selector (`..`) allows you to jump to the parent element. + +Example #1: + +Given this HTML: + +```html + + +
Unique Value
+ + +``` + +We can locate the `` element with this selector: + +```xpath +//*[text()='Unique Value']/../.. +``` + +Example #2: + +Given this HTML: + +```html + + + Edit + + +
Unique Value
+ + +``` + +We can locate the `` element with this selector: + +```xpath +//div[text()='Unique Value']/../..//a +``` + +#### Attribute Selectors + +Attribute selectors allow you to select elements that match a specific attribute value. + +Examples: + +Attribute|HTML|Selector +---|---|--- +id|`
`|`//*[@id='idname']` +class|`
`|`//*[@class='classname']` +type|`