diff --git a/.gitattributes b/.gitattributes index 47a5c0fdf..2fdc4ff88 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,11 +2,14 @@ /scripts export-ignore /tests export-ignore +/tools export-ignore /.gitattributes export-ignore /.gitignore export-ignore /example.php export-ignore /phpunit.xml.dist export-ignore /.github export-ignore -/.php_cs.dist export-ignore +/.php-cs-fixer.dist.php export-ignore /phpstan.neon export-ignore /.coveralls.yml export-ignore +/logs export-ignore +/mlc_config.json export-ignore diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c10d85094..a79b02b1f 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -27,8 +27,6 @@ Here is the [official documentation](https://www.selenium.dev/documentation/en/) To make sure your code comply with [PSR-2](http://www.php-fig.org/psr/psr-2/) coding style, tests passes and to execute other automated checks, run locally: ```sh -composer preinstall # Run this only on the first run - this will install some additional dependencies - composer all ``` @@ -63,7 +61,7 @@ then start the local PHP server which will serve the test pages and then run the ```sh export BROWSER_NAME="htmlunit" # see below for other browsers -java -jar selenium-server-standalone-X.X.X.jar -log selenium.log & +java -jar selenium-server-X.XX.0.jar standalone --log selenium.log & php -S localhost:8000 -t tests/functional/web/ & # Use following to run both unit and functional tests composer all @@ -71,7 +69,7 @@ composer all composer test -- --testsuite functional ``` -If you want to run tests in different browser then "htmlunit" (Chrome or Firefox), you need to setup the browser driver (Chromedriver/Geckodriver), as it is [explained in wiki](https://github.com/php-webdriver/php-webdriver/wiki/Chrome) +If you want to run tests in different browser then "htmlunit" (Chrome or Firefox), you need to set up the browser driver (Chromedriver/Geckodriver), as it is [explained in wiki](https://github.com/php-webdriver/php-webdriver/wiki/Chrome) and then the `BROWSER_NAME` environment variable: ```sh @@ -87,3 +85,10 @@ export GECKODRIVER=1 export BROWSER_NAME="firefox" composer all ``` + +To see the tests as they are happening (in the browser window), you can disable headless mode. This is useful eg. when debugging the tests or writing a new one: + +```sh +export DISABLE_HEADLESS="1" +composer all +``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index dcfc43785..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -name: 🐛 Bug report -about: Create a bug report to help us improve php-webdriver -title: '' -labels: bug -assignees: '' ---- - - - -### Bug description - - -### How could the issue be reproduced - -Steps to reproduce the behavior: -1. -2. -3. ... - - - -```php -// You can insert your PHP code here (or remove this block if it is not relevant for the issue). - -// For example you can provide how you create WebDrivere instance: -$capabilities = DesiredCapabilities::chrome(); -$driver = RemoteWebDriver::create('http://localhost:4444/', $capabilities); - -// And the code you use to execute the php-webdriver commands, for example: -$driver->get('http://site.localhost/foo.html'); -$button = $driver->findElement(WebDriverBy::cssSelector('#foo')); -$button->click(); -``` - -```html - -
- -
-``` - -### Expected behavior - - -### Details - - -* Php-webdriver version: -* PHP version: -* How do you start the browser driver or Selenium server: - - -* Selenium server version: -* Browser driver (chromedriver/geckodriver...) version: -* Browser used + version: -* Operating system: - -### Additional context - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..bac315d1d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,124 @@ +name: 🐛 Bug report +description: Create a bug report to help us improve php-webdriver +labels: [ "bug" ] +body: + - type: markdown + attributes: + value: | + If you have a question, [ask in Discussions](https://github.com/php-webdriver/php-webdriver/discussions) instead of filling a bug. + + If you are reporting a bug, please **fill as much as possible information**, otherwise the community and maintainers cannot provide a prompt feedback and help solving the issue. + - type: textarea + id: bug-description + attributes: + label: Bug description + description: | + A clear description of what the bug is. + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: How could the issue be reproduced + description: | + Provide steps to reproduce the behavior. Please include everything relevant - the PHP code you use to initialize driver instance, the PHP code causing the error, HTML snippet or URL of the page where you encounter the issue etc. + This will be automatically formatted into code, so no need for backticks ```. + placeholder: | + // For example you can provide how you create WebDriver instance: + $capabilities = DesiredCapabilities::chrome(); + $driver = RemoteWebDriver::create('http://localhost:4444/', $capabilities); + // And the code you use to execute the php-webdriver commands, for example: + $driver->get('http://site.localhost/foo.html'); + $button = $driver->findElement(WebDriverBy::cssSelector('#foo')); + $button->click(); + +
+ +
+ + render: shell + validations: + required: true + + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: | + A clear and concise description of what you expected to happen. + validations: + required: false + + - type: input + id: php-webdriver-version + attributes: + label: Php-webdriver version + description: You can run `composer show php-webdriver/webdriver` to find the version number + placeholder: | + For example: 1.13.0 + validations: + required: true + + - type: input + id: php-version + attributes: + label: PHP version + description: You can run `php -v` to find the version + placeholder: | + For example: 8.1.11 + validations: + required: true + + - type: input + id: how-start + attributes: + label: How do you start the browser driver or Selenium server + description: | + For example: Selenium server jar, Selenium in Docker, chromedriver command, Laravel Dusk, SauceLabs etc. + If relevant, provide the complete command you use to start the browser driver or Selenium server + validations: + required: true + + - type: input + id: selenium-version + attributes: + label: Selenium server / Selenium Docker image version + description: Relevant only if you use Selenium server / Selenium in Docker + validations: + required: false + + - type: input + id: browser-driver + attributes: + label: Browser driver (chromedriver/geckodriver...) version + description: You can run `chromedriver --version` or `geckodriver --version` to find the version + placeholder: | + For example: geckodriver 0.31.0 + validations: + required: false + + - type: input + id: browser + attributes: + label: Browser name and version + placeholder: | + For example: Firefox 105.0.2 + validations: + required: false + + - type: input + id: operating-system + attributes: + label: Operating system + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: | + Add any other context or you notes about the problem here. + validations: + required: false diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 000000000..50112e87a --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily + time: "04:00" diff --git a/.github/no-response.yml b/.github/no-response.yml deleted file mode 100644 index 320baf22f..000000000 --- a/.github/no-response.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Configuration for probot-no-response - https://github.com/probot/no-response - -# Number of days of inactivity before an Issue is closed for lack of response -daysUntilClose: 14 - -# Label requiring a response -responseRequiredLabel: "waiting for reaction" - -# Comment to post when closing an Issue for lack of response. Set to `false` to disable -closeComment: > - This issue has been automatically closed because there has been no response - to our request for more information from the original author. With only the - information that is currently in the issue, we don't have enough information - to take action. - - If the original issue author adds comment with more information, - this issue will be automatically reopened and we can investigate further. diff --git a/.github/workflows/coveralls-workaround.yaml b/.github/workflows/coveralls-workaround.yaml new file mode 100644 index 000000000..d6044861d --- /dev/null +++ b/.github/workflows/coveralls-workaround.yaml @@ -0,0 +1,48 @@ +name: Coveralls coverage +# Must be run in separate workflow to have access to repository secrets even for PR from forks. +# See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + +permissions: + contents: read + +on: + workflow_run: + workflows: [ "Tests" ] + types: + - completed + +jobs: + coveralls: + name: Coveralls coverage workaround + runs-on: ubuntu-latest + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' + steps: + # see https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + - name: 'Download artifact' + uses: actions/github-script@v8 + with: + script: | + var artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{github.event.workflow_run.id }}, + }); + var matchArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "data" + })[0]; + var download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + var fs = require('fs'); + fs.writeFileSync('${{github.workspace}}/data.zip', Buffer.from(download.data)); + - run: unzip data.zip + - name: Coveralls coverage workaround + # see https://github.com/lemurheavy/coveralls-public/issues/1653#issuecomment-1251587119 + run: | + BUILD_NUM=$(cat run_id) + curl --location --request GET "https://coveralls.io/rerun_build?repo_token=${{%20secrets.COVERALLS_REPO_TOKEN%20}}&build_num=$BUILD_NUM" diff --git a/.github/workflows/docs-lint.yml b/.github/workflows/docs-lint.yml new file mode 100644 index 000000000..42baa0000 --- /dev/null +++ b/.github/workflows/docs-lint.yml @@ -0,0 +1,26 @@ +name: Lint PHP documentation + +permissions: + contents: read + +on: + push: + pull_request: + branches: + - 'main' + +jobs: + lint-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Lint PHP documentation + uses: sudo-bot/action-doctum@v5 + with: + config-file: scripts/doctum.php + method: 'parse' + cli-args: '--output-format=github --no-ansi --no-progress -v --ignore-parse-errors' diff --git a/.github/workflows/docs-publish.yml b/.github/workflows/docs-publish.yml new file mode 100644 index 000000000..d0da12f8b --- /dev/null +++ b/.github/workflows/docs-publish.yml @@ -0,0 +1,38 @@ +name: Publish API documentation + +permissions: + contents: read + +on: + repository_dispatch: + types: [ run-build-api-docs ] + workflow_dispatch: + schedule: + - cron: "00 12 * * *" + +jobs: + publish-pages: + environment: + name: API documentation + url: https://php-webdriver.github.io/php-webdriver/ + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + ssh-key: ${{ secrets.SSH_KEY_DEPLOY }} + + - name: Build PHP documentation + uses: sudo-bot/action-doctum@v5 + with: + config-file: scripts/doctum.php + method: 'update' + cli-args: '--output-format=github --no-ansi --no-progress -v --ignore-parse-errors' + + - name: Set commit author + run: | + git config user.name "Automated" + git config user.email "actions@users.noreply.github.com" + - name: Push the changes + run: ./scripts/update-built-docs.sh diff --git a/.github/workflows/no-response.yaml b/.github/workflows/no-response.yaml new file mode 100644 index 000000000..7147e3792 --- /dev/null +++ b/.github/workflows/no-response.yaml @@ -0,0 +1,29 @@ +name: No Response + +permissions: + issues: write + +# Both `issue_comment` and `scheduled` event types are required for this Action to work properly. +on: + issue_comment: + types: [created] + schedule: + - cron: '* */8 * * *' # every hour at :33 + +jobs: + noResponse: + runs-on: ubuntu-latest + steps: + - uses: lee-dohm/no-response@v0.5.0 + with: + token: ${{ github.token }} + daysUntilClose: 14 + responseRequiredLabel: 'waiting for reaction' + closeComment: > + This issue has been automatically closed because there has been no response + to our request for more information from the original author. With only the + information that is currently in the issue, we don't have enough information + to take action. + + If the original issue author adds comment with more information, + this issue will be automatically reopened and we can investigate further. diff --git a/.github/workflows/sauce-labs.yaml b/.github/workflows/sauce-labs.yaml index e183bdfac..812fbfe25 100644 --- a/.github/workflows/sauce-labs.yaml +++ b/.github/workflows/sauce-labs.yaml @@ -1,5 +1,8 @@ name: Sauce Labs +permissions: + contents: read + on: push: schedule: @@ -8,6 +11,8 @@ on: jobs: tests: runs-on: ubuntu-latest + # Source: https://github.community/t/do-not-run-cron-workflows-in-forks/17636/2 + if: (github.event_name == 'schedule' && github.repository == 'php-webdriver/php-webdriver') || (github.event_name != 'schedule') env: SAUCELABS: 1 SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} @@ -15,21 +20,20 @@ jobs: strategy: fail-fast: false matrix: - w3c: [true] include: # Chrome 74 is the last version which doesn't use W3C WebDriver by default and rather use OSS protocol - - { name: "Chrome 74, OSS protocol", BROWSER_NAME: "chrome", VERSION: "74.0", PLATFORM: "Windows 10", w3c: false, tunnel-id: "gh-1" } - - { name: "Chrome latest, W3C protocol", BROWSER_NAME: "chrome", VERSION: "latest", PLATFORM: "Windows 10", tunnel-id: "gh-2" } - - { name: "Edge latest, W3C protocol", BROWSER_NAME: "MicrosoftEdge", VERSION: "latest", PLATFORM: "Windows 10", tunnel-id: "gh-3" } + - { name: "Chrome 74, OSS protocol", BROWSER_NAME: "chrome", VERSION: "74.0", PLATFORM: "Windows 11", w3c: false, tunnel-name: "gh-1-chrome-oss-legacy" } + - { name: "Chrome latest, W3C protocol", BROWSER_NAME: "chrome", VERSION: "latest", PLATFORM: "Windows 11", w3c: true, tunnel-name: "gh-2-chrome-w3c" } + - { name: "Edge latest, W3C protocol", BROWSER_NAME: "MicrosoftEdge", VERSION: "latest", PLATFORM: "Windows 11", w3c: true, tunnel-name: "gh-3-MicrosoftEdge" } - name: ${{ matrix.name }} (${{ matrix.tunnel-id }}) + name: ${{ matrix.name }} (${{ matrix.tunnel-name }}) steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.3' extensions: mbstring, intl, zip coverage: none @@ -41,11 +45,13 @@ jobs: php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & - name: Start Sauce Connect - uses: saucelabs/sauce-connect-action@v1 + uses: saucelabs/sauce-connect-action@v3 with: username: ${{ secrets.SAUCE_USERNAME }} accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} - tunnelIdentifier: ${{ matrix.tunnel-id }} + tunnelName: ${{ matrix.tunnel-name }} + proxyLocalhost: allow + region: 'us-west-1' - name: Run tests env: @@ -53,7 +59,7 @@ jobs: VERSION: ${{ matrix.VERSION }} PLATFORM: ${{ matrix.PLATFORM }} DISABLE_W3C_PROTOCOL: "${{ matrix.w3c && '0' || '1' }}" - SAUCE_TUNNEL_IDENTIFIER: ${{ matrix.tunnel-id }} + SAUCE_TUNNEL_NAME: ${{ matrix.tunnel-name }} run: | if [ -n "$SAUCELABS" ]; then EXCLUDE_GROUP+="exclude-saucelabs,"; fi if [ "$BROWSER_NAME" = "MicrosoftEdge" ]; then EXCLUDE_GROUP+="exclude-edge,"; fi diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 45e5761e9..d215e92ce 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,5 +1,8 @@ name: Tests +permissions: + contents: read + on: push: pull_request: @@ -11,16 +14,16 @@ jobs: name: "Code style and static analysis" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.3' extensions: mbstring, intl, zip - name: Install PHP dependencies - run: composer preinstall + run: composer update --no-interaction - name: Lint run: composer lint @@ -32,8 +35,8 @@ jobs: name: "Markdown link check" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: gaurav-nelson/github-action-markdown-link-check@v1 + - uses: actions/checkout@v6 + - uses: tcort/github-action-markdown-link-check@v1 with: use-verbose-mode: 'yes' @@ -43,14 +46,13 @@ jobs: strategy: matrix: - php-version: ['5.6', '7.1', '7.2', '7.3', '7.4', '8.0'] + php-version: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] dependencies: [''] include: - - { php-version: '5.6', dependencies: '--prefer-lowest' } - - { php-version: '8.1', dependencies: '--ignore-platform-req=php' } + - { php-version: '7.3', dependencies: '--prefer-lowest' } steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -63,10 +65,6 @@ jobs: - name: Install PHP dependencies run: composer update --no-interaction ${{ matrix.dependencies }} - - name: Apply PHPUnit patches - if: ${{ matrix.php-version < 7.2 }} - run: scripts/apply-phpunit-patches.sh - - name: Run tests run: vendor/bin/phpunit --testsuite unit --colors=always --coverage-clover ./logs/clover.xml @@ -84,7 +82,7 @@ jobs: functional-tests: runs-on: ${{ matrix.os }} env: - SELENIUM_SERVER_DOWNLOAD_URL: http://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar + SELENIUM_SERVER_DOWNLOAD_URL: https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.38.0/selenium-server-4.38.0.jar strategy: fail-fast: false @@ -101,12 +99,12 @@ jobs: name: "Functional tests (${{ matrix.browser }}, Selenium server: ${{ matrix.selenium-server }}, W3C: ${{ matrix.w3c }})" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: '8.3' extensions: mbstring, intl, zip coverage: xdebug @@ -116,20 +114,20 @@ jobs: - name: Start Selenium standalone server # If you want to run your Selenium WebDriver tests on GitHub actions, we recommend using service containers # with eg. selenium/standalone-chrome image. See https://docs.github.com/en/actions/guides/about-service-containers - # But for the purpose of testing this library itself, we need more control so we set everything up manually. + # But for the purpose of testing this library itself, we need more control, so we set everything up manually. if: ${{ matrix.selenium-server }} run: | - mkdir -p build - wget -q -t 3 -O build/selenium-server-standalone.jar $SELENIUM_SERVER_DOWNLOAD_URL - java -jar build/selenium-server-standalone.jar -version - xvfb-run --server-args="-screen 0, 1280x720x24" --auto-servernum java -jar build/selenium-server-standalone.jar -log logs/selenium-server.log & + mkdir -p build logs + wget -q -t 3 -O build/selenium-server.jar $SELENIUM_SERVER_DOWNLOAD_URL + java -jar build/selenium-server.jar standalone --version + xvfb-run --server-args="-screen 0, 1280x720x24" --auto-servernum java -jar build/selenium-server.jar standalone --log logs/selenium-server.log & - name: Start ChromeDriver if: ${{ !matrix.selenium-server && matrix.browser == 'chrome' }} run: | google-chrome --version xvfb-run --server-args="-screen 0, 1280x720x24" --auto-servernum \ - chromedriver --port=4444 --url-base=/wd/hub &> ./logs/chromedriver.log & + chromedriver --port=4444 &> ./logs/chromedriver.log & - name: Start GeckoDriver if: ${{ !matrix.selenium-server && matrix.browser == 'firefox' }} @@ -150,6 +148,7 @@ jobs: php -S 127.0.0.1:8000 -t tests/functional/web/ &> ./logs/php-server.log & - name: Wait for browser & PHP to start + timeout-minutes: 1 run: | while ! nc -z localhost 4444 ./data/run_id + - uses: actions/upload-artifact@v6 + with: + name: data + path: data/ diff --git a/.gitignore b/.gitignore index d5605367c..a9384110d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ composer.phar composer.lock vendor +tools/php-cs-fixer/vendor .php_cs.cache +.php-cs-fixer.cache .phpunit.result.cache phpunit.xml logs/ +build/ # generic files to ignore *.lock diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 000000000..f754fb8b7 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,139 @@ +notPath('Firefox/FirefoxProfile.php') // need to use str_* instead of mb_str_* methods + ->in([__DIR__ . '/lib', __DIR__ . '/tests']); + +return (new PhpCsFixer\Config()) + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => true, + 'blank_line_before_statement' => ['statements' => ['return', 'try']], + 'braces' => ['allow_single_line_anonymous_class_with_empty_body' => true, 'allow_single_line_closure' => true], + 'cast_spaces' => true, + 'class_attributes_separation' => ['elements' => ['method' => 'one', 'trait_import' => 'none']], + 'clean_namespace' => true, + 'combine_nested_dirname' => true, + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => true, + //'declare_strict_types' => true, // TODO: used only in tests, use in lib in next major version + 'fopen_flag_order' => true, + 'fopen_flags' => true, + 'full_opening_tag' => true, + 'function_typehint_space' => true, + 'heredoc_indentation' => ['indentation' => 'same_as_start'], + 'implode_call' => true, + 'is_null' => true, + 'lambda_not_used_import' => true, + 'list_syntax' => true, + 'lowercase_cast' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'mb_str_functions' => true, + 'method_argument_space' => ['after_heredoc' => true], + 'native_function_casing' => true, + 'native_function_type_declaration_casing' => true, + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'normalize_index_brace' => true, + 'no_extra_blank_lines' => [ + 'tokens' => [ + 'break', + 'case', + 'continue', + 'curly_brace_block', + 'default', + 'extra', + 'parenthesis_brace_block', + 'return', + 'square_brace_block', + 'switch', + 'throw', + 'use', + ], + ], + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_superfluous_phpdoc_tags' => [ + 'allow_mixed' => true, + 'remove_inheritdoc' => true, + 'allow_unused_params' => true, // Used in RemoteWebDriver::createBySessionID to maintain BC + ], + 'no_trailing_comma_in_singleline' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'no_useless_sprintf' => true, + 'no_whitespace_before_comma_in_array' => ['after_heredoc' => true], + 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => true, + 'nullable_type_declaration' => [ + 'syntax' => 'question_mark', + ], + 'nullable_type_declaration_for_default_null_value' => true, + 'object_operator_without_whitespace' => true, + 'ordered_class_elements' => true, + 'ordered_imports' => true, + 'php_unit_construct' => true, + 'php_unit_dedicate_assert' => true, + 'php_unit_dedicate_assert_internal_type' => true, + 'php_unit_expectation' => ['target' => '8.4'], + 'php_unit_method_casing' => ['case' => 'camel_case'], + 'php_unit_mock_short_will_return' => true, + 'php_unit_mock' => true, + 'php_unit_namespaced' => ['target' => '6.0'], + 'php_unit_no_expectation_annotation' => true, + 'php_unit_set_up_tear_down_visibility' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], + 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_indent' => true, + 'phpdoc_no_access' => true, + // 'phpdoc_no_empty_return' => true, // disabled to allow forward compatibility with PHP 8.1 + 'phpdoc_no_package' => true, + 'phpdoc_order_by_value' => ['annotations' => ['covers', 'group', 'throws']], + 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_trim' => true, + //'phpdoc_to_param_type' => true, // TODO: used only in tests, use in lib in next major version + //'phpdoc_to_return_type' => true, // TODO: used only in tests, use in lib in next major version + 'phpdoc_types' => true, + 'phpdoc_var_annotation_correct_order' => true, + 'pow_to_exponentiation' => true, + 'psr_autoloading' => true, + 'random_api_migration' => true, + 'self_accessor' => true, + 'set_type_to_cast' => true, + 'short_scalar_cast' => true, + 'single_blank_line_before_namespace' => true, + 'single_quote' => true, + 'single_space_after_construct' => true, + 'single_trait_insert_per_statement' => true, + 'space_after_semicolon' => true, + 'standardize_not_equals' => true, + 'strict_param' => true, + 'switch_continue_to_break' => true, + 'ternary_operator_spaces' => true, + 'ternary_to_elvis_operator' => true, + 'ternary_to_null_coalescing' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays'], 'after_heredoc' => true], + 'trim_array_spaces' => true, + 'unary_operator_spaces' => true, + 'visibility_required' => ['elements' => ['method', 'property', 'const']], + //'void_return' => true, // TODO: used only in tests, use in lib in next major version + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], + ]) + ->setRiskyAllowed(true) + ->setFinder($finder); diff --git a/.php_cs.dist b/.php_cs.dist deleted file mode 100644 index a438f2095..000000000 --- a/.php_cs.dist +++ /dev/null @@ -1,91 +0,0 @@ -in([__DIR__ . '/lib', __DIR__ . '/tests']); - -return PhpCsFixer\Config::create() - ->setRules([ - '@PSR2' => true, - 'array_syntax' => ['syntax' => 'short'], - 'binary_operator_spaces' => true, - 'blank_line_before_return' => true, - 'cast_spaces' => true, - 'concat_space' => ['spacing' => 'one'], - 'function_typehint_space' => true, - 'general_phpdoc_annotation_remove' => ['author'], - 'implode_call' => true, - 'is_null' => true, - 'linebreak_after_opening_tag' => true, - 'lowercase_cast' => true, - 'mb_str_functions' => true, - 'method_separation' => true, - 'native_function_casing' => true, - 'new_with_braces' => true, - 'no_alias_functions' => true, - 'no_blank_lines_after_class_opening' => true, - 'no_blank_lines_after_phpdoc' => true, - 'no_empty_comment' => true, - 'no_empty_phpdoc' => true, - 'no_empty_statement' => true, - 'no_extra_consecutive_blank_lines' => [ - 'use', - 'break', - 'continue', - 'extra', - 'return', - 'throw', - 'useTrait', - 'curly_brace_block', - 'parenthesis_brace_block', - 'square_brace_block', - ], - 'no_leading_import_slash' => true, - 'no_leading_namespace_whitespace' => true, - 'no_singleline_whitespace_before_semicolons' => true, - 'no_trailing_comma_in_singleline_array' => true, - 'no_unreachable_default_argument_value' => true, - 'no_unused_imports' => true, - 'no_useless_else' => true, - 'no_useless_return' => true, - 'no_whitespace_in_blank_line' => true, - 'object_operator_without_whitespace' => true, - 'ordered_class_elements' => true, - 'ordered_imports' => true, - 'php_unit_construct' => true, - 'php_unit_dedicate_assert' => false, - 'php_unit_expectation' => ['target' => '5.6'], - 'php_unit_method_casing' => ['case' => 'camel_case'], - 'php_unit_mock' => true, - 'php_unit_mock_short_will_return' => true, - 'php_unit_namespaced' => ['target' => '5.7'], - 'php_unit_no_expectation_annotation' => true, - 'php_unit_ordered_covers' => true, - 'php_unit_set_up_tear_down_visibility' => true, - 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], - 'phpdoc_add_missing_param_annotation' => true, - 'phpdoc_indent' => true, - 'phpdoc_no_access' => true, - 'phpdoc_no_empty_return' => true, - 'phpdoc_no_package' => true, - 'phpdoc_order' => true, - 'phpdoc_scalar' => true, - 'phpdoc_single_line_var_spacing' => true, - 'phpdoc_trim' => true, - 'phpdoc_types' => true, - 'psr4' => true, - 'self_accessor' => true, - 'short_scalar_cast' => true, - 'single_blank_line_before_namespace' => true, - 'single_quote' => true, - 'space_after_semicolon' => true, - 'standardize_not_equals' => true, - 'ternary_operator_spaces' => true, - 'trailing_comma_in_multiline_array' => true, - 'trim_array_spaces' => true, - 'unary_operator_spaces' => true, - 'visibility_required' => true, - 'whitespace_after_comma_in_array' => true, - 'yoda_style' => false, - ]) - ->setRiskyAllowed(true) - ->setFinder($finder); diff --git a/CHANGELOG.md b/CHANGELOG.md index 085f24450..a468c26f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,84 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +## 1.15.2 - 2024-11-21 +### Fixed +- PHP 8.4 deprecation notices, especially in nullable type-hints. +- Docs: Fix static return types in RemoteWebElement phpDoc. +- Tests: Disable chrome 127+ search engine pop-up in tests +- Tests: Enable Shadow DOM tests in Geckodriver + +### Added +- Tests: Allow running tests in headfull (not headless) mode using `DISABLE_HEADLESS` environment variable. + +### Changed +- Docs: Update selenium server host URL in example. + +## 1.15.1 - 2023-10-20 +- Update `symfony/process` dependency to support upcoming Symfony 7. + +## 1.15.0 - 2023-08-29 +### Changed +- Capability key `ChromeOptions::CAPABILITY_W3C` used to set ChromeOptions is now deprecated in favor of `ChromeOptions::CAPABILITY`, which now also contains the W3C compatible value (`goog:chromeOptions`). +- ChromeOptions are now passed to the driver always as a W3C compatible key `goog:chromeOptions`, even in the deprecated OSS JsonWire payload (as ChromeDriver [supports](https://bugs.chromium.org/p/chromedriver/issues/detail?id=1786) this since 2017). +- Improve Safari compatibility for ` is shown. + let enclosingSelectElement = enclosingNodeOrSelfMatchingPredicate(element, (e) => e.tagName.toUpperCase() === 'SELECT'); + return isElementDisplayed(enclosingSelectElement); + } + case 'INPUT': + // is considered not shown. + if (element.type === 'hidden') { + return false; + } + break; + // case 'MAP': + // FIXME: Selenium has special handling for elements. We don't do anything now. + default: + break; + } + if (cascadedStylePropertyForElement(element, 'visibility') !== 'visible') { + return false; + } + let hasAncestorWithZeroOpacity = !!enclosingElementOrSelfMatchingPredicate(element, (e) => { + return Number(cascadedStylePropertyForElement(e, 'opacity')) === 0; + }); + let hasAncestorWithDisplayNone = !!enclosingElementOrSelfMatchingPredicate(element, (e) => { + return cascadedStylePropertyForElement(e, 'display') === 'none'; + }); + if (hasAncestorWithZeroOpacity || hasAncestorWithDisplayNone) { + return false; + } + if (!elementSubtreeHasNonZeroDimensions(element)) { + return false; + } + if (isElementSubtreeHiddenByOverflow(element)) { + return false; + } + return true; +} + diff --git a/mlc_config.json b/mlc_config.json new file mode 100644 index 000000000..5d70a03e0 --- /dev/null +++ b/mlc_config.json @@ -0,0 +1,7 @@ +{ + "ignorePatterns": [ + { + "pattern": "^https://stackoverflow\\.com/questions/tagged/php\\+selenium-webdriver" + } + ] +} diff --git a/phpstan.neon b/phpstan.neon index 846484976..b4c1e307d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,14 +5,25 @@ parameters: - tests/ ignoreErrors: - # To be fixed: - - '#Call to an undefined method Facebook\\WebDriver\\WebDriver::getTouch\(\)#' - - '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::getCoordinates\(\)#' - - '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::equals\(\)#' + # To be fixed in next major version: + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriver::getTouch\(\)#' + path: 'lib/Interactions/WebDriverTouchActions.php' + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriver::getTouch\(\)#' + path: 'lib/Support/Events/EventFiringWebDriver.php' + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::getCoordinates\(\)#' + path: 'lib/Support/Events/EventFiringWebElement.php' + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::equals\(\)#' + path: 'lib/Support/Events/EventFiringWebElement.php' + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::takeElementScreenshot\(\)#' + path: 'lib/Support/Events/EventFiringWebElement.php' + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::getShadowRoot\(\)#' + path: 'lib/Support/Events/EventFiringWebElement.php' - '#Unsafe usage of new static\(\)#' # Parameter is intentionally not part of signature to not break BC - message: '#PHPDoc tag \@param references unknown parameter: \$isW3cCompliant#' path: 'lib/Remote/RemoteWebDriver.php' + - message: '#PHPDoc tag \@param references unknown parameter: \$existingCapabilities#' + path: 'lib/Remote/RemoteWebDriver.php' inferPrivatePropertyTypeFromConstructor: true diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2e81aef6d..c58c72eaa 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,14 +1,9 @@ - - - - + tests/unit @@ -18,14 +13,13 @@ - - + + ./lib - - + + - diff --git a/scripts/apply-phpunit-patches.sh b/scripts/apply-phpunit-patches.sh deleted file mode 100755 index 478e01c74..000000000 --- a/scripts/apply-phpunit-patches.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -# All commands below must no fail -set -e - -# Be in the root dir -cd "$(dirname $0)/../" - -find tests/ -type f -print0 | xargs -0 sed -i 's/function setUpBeforeClass(): void/function setUpBeforeClass()/g'; -find tests/ -type f -print0 | xargs -0 sed -i 's/function setUp(): void/function setUp()/g'; -find tests/ -type f -print0 | xargs -0 sed -i 's/function tearDown(): void/function tearDown()/g'; - -sed -i 's/endTest(\\PHPUnit\\Framework\\Test \$test, float \$time): void/endTest(\\PHPUnit_Framework_Test \$test, \$time)/g' tests/functional/ReportSauceLabsStatusListener.php; -sed -i 's/: void/ /g' tests/functional/ReportSauceLabsStatusListener.php; -# Drop the listener from the config file -sed -i '//,+2d' phpunit.xml.dist; -sed -i 's/function runBare(): void/function runBare()/g' tests/functional/WebDriverTestCase.php; - -# Return back to original dir -cd - > /dev/null diff --git a/scripts/docs-template.html b/scripts/docs-template.html new file mode 100644 index 000000000..4814f5ec5 --- /dev/null +++ b/scripts/docs-template.html @@ -0,0 +1,7 @@ + + + + + Taking you to the latest documentation. + + \ No newline at end of file diff --git a/scripts/doctum.php b/scripts/doctum.php new file mode 100644 index 000000000..08e722917 --- /dev/null +++ b/scripts/doctum.php @@ -0,0 +1,30 @@ +files() + ->name('*.php') + ->in($srcRoot . 'lib'); + +$versions = GitVersionCollection::create($srcRoot) + ->addFromTags('1.*') // only latest minor version + ->addFromTags('0.6.0') + ->add('main', 'main branch') +; + +return new Doctum($iterator, [ + 'title' => 'php-webdriver API', + 'theme' => 'default', + 'build_dir' => $root . '/build/dist/%version%/', + 'cache_dir' => $root . '/build/cache/%version%/', + 'include_parent_data' => true, + 'remote_repository' => new GitHubRemoteRepository('php-webdriver/php-webdriver', $srcRoot), + 'versions' => $versions, + 'base_url' => 'https://php-webdriver.github.io/php-webdriver/%version%/' +]); diff --git a/scripts/update-built-docs.sh b/scripts/update-built-docs.sh new file mode 100755 index 000000000..76646bed5 --- /dev/null +++ b/scripts/update-built-docs.sh @@ -0,0 +1,50 @@ +#!/bin/sh +set -e + +cleanup() { + git ls-files ./ | xargs -r -n 1 rm + rm -rfd ./* +} + +copyToTemp() { + TEMP_DIR="$(mktemp -d --suffix=_doctum-build-php-webdriver)" + cp -rp build/dist/* "${TEMP_DIR}" + cp ./scripts/docs-template.html "${TEMP_DIR}/index.html" +} + +emptyAndRemoveTemp() { + mv "${TEMP_DIR}"/* ./ + # Create symlink for main to latest + ln -s -r ./main ./latest + # Create symlink for main to master + ln -s -r ./main ./master + # Create symlink for main to community + ln -s -r ./main ./community + rm -rf "${TEMP_DIR}" +} + +commitAndPushChanges() { + # Push the changes, only if there is changes + git add -A + git diff-index --quiet HEAD || git commit -m "Api documentations update ($(date --rfc-3339=seconds --utc))" -m "#apidocs" && if [ -z "${SKIP_PUSH}" ]; then git push; fi +} + +if [ ! -d ./build/dist ]; then + echo 'Missing built docs' + exit 1 +fi + +# Remove cache dir, do not upload it +rm -rf ./build/cache + +copyToTemp +# Remove build dir, do not upload it +rm -rf ./build + +git checkout gh-pages + +cleanup +emptyAndRemoveTemp +commitAndPushChanges + +git checkout - > /dev/null diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 6c8c4f51b..d9cb0c2c7 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,3 +1,3 @@ -driver); @@ -31,7 +31,7 @@ public function testShouldExecuteDevToolsCommandWithoutParameters() $this->assertSame([], $result); } - public function testShouldExecuteDevToolsCommandWithParameters() + public function testShouldExecuteDevToolsCommandWithParameters(): void { $devTools = new ChromeDevToolsDriver($this->driver); diff --git a/tests/functional/Chrome/ChromeDriverServiceTest.php b/tests/functional/Chrome/ChromeDriverServiceTest.php index d83ca6af4..9b6c46336 100644 --- a/tests/functional/Chrome/ChromeDriverServiceTest.php +++ b/tests/functional/Chrome/ChromeDriverServiceTest.php @@ -1,4 +1,4 @@ -assertInstanceOf(ChromeDriverService::class, $this->driverService->stop()); } - public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor() + public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor(): void { $this->driverService = new ChromeDriverService(getenv('CHROMEDRIVER_PATH'), 9515, ['--port=9515']); @@ -63,7 +63,7 @@ public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor() $this->assertFalse($this->driverService->isRunning()); } - public function testShouldThrowExceptionIfExecutableIsNotExecutable() + public function testShouldThrowExceptionIfExecutableIsNotExecutable(): void { putenv(ChromeDriverService::CHROME_DRIVER_EXECUTABLE . '=' . __FILE__); @@ -72,7 +72,7 @@ public function testShouldThrowExceptionIfExecutableIsNotExecutable() ChromeDriverService::createDefaultService(); } - public function testShouldUseDefaultExecutableIfNoneProvided() + public function testShouldUseDefaultExecutableIfNoneProvided(): void { // Put path where ChromeDriver binary is actually located to system PATH, to make sure we can locate it putenv('PATH=' . getenv('PATH') . ':' . dirname(getenv('CHROMEDRIVER_PATH'))); diff --git a/tests/functional/Chrome/ChromeDriverTest.php b/tests/functional/Chrome/ChromeDriverTest.php index 695fa57e3..407b65bd8 100644 --- a/tests/functional/Chrome/ChromeDriverTest.php +++ b/tests/functional/Chrome/ChromeDriverTest.php @@ -1,4 +1,4 @@ -startChromeDriver($isW3cDialect); $this->assertInstanceOf(ChromeDriver::class, $this->driver); @@ -55,7 +54,7 @@ public function testShouldStartChromeDriver($isW3cDialect) /** * @return array[] */ - public function provideDialect() + public function provideDialect(): array { return [ 'w3c' => [true], @@ -63,7 +62,7 @@ public function provideDialect() ]; } - public function testShouldInstantiateDevTools() + public function testShouldInstantiateDevTools(): void { $this->startChromeDriver(); @@ -81,14 +80,14 @@ public function testShouldInstantiateDevTools() $this->assertSame(['result' => ['type' => 'string', 'value' => 'http://localhost:8000/']], $cdpResult); } - private function startChromeDriver($w3cDialect = true) + private function startChromeDriver($w3cDialect = true): void { // The createDefaultService() method expect path to the executable to be present in the environment variable putenv(ChromeDriverService::CHROME_DRIVER_EXECUTABLE . '=' . getenv('CHROMEDRIVER_PATH')); // Add --no-sandbox as a workaround for Chrome crashing: https://github.com/SeleniumHQ/selenium/issues/4961 $chromeOptions = new ChromeOptions(); - $chromeOptions->addArguments(['--no-sandbox', '--headless']); + $chromeOptions->addArguments(['--no-sandbox', '--headless', '--disable-search-engine-choice-screen']); $chromeOptions->setExperimentalOption('w3c', $w3cDialect); $desiredCapabilities = DesiredCapabilities::chrome(); $desiredCapabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); diff --git a/tests/functional/FileUploadTest.php b/tests/functional/FileUploadTest.php index f4728be41..e812353ea 100644 --- a/tests/functional/FileUploadTest.php +++ b/tests/functional/FileUploadTest.php @@ -1,4 +1,4 @@ -driver->get($this->getTestPageUrl('upload.html')); + $this->driver->get($this->getTestPageUrl(TestPage::UPLOAD)); $fileElement = $this->driver->findElement(WebDriverBy::name('upload')); @@ -43,7 +45,7 @@ public function testShouldUploadAFile() $this->assertSame('10', $uploadedFileSize); } - private function getTestFilePath() + private function getTestFilePath(): string { return __DIR__ . '/Fixtures/FileUploadTestFile.txt'; } diff --git a/tests/functional/Firefox/FirefoxDriverServiceTest.php b/tests/functional/Firefox/FirefoxDriverServiceTest.php index e39259e2b..feabe0dbf 100644 --- a/tests/functional/Firefox/FirefoxDriverServiceTest.php +++ b/tests/functional/Firefox/FirefoxDriverServiceTest.php @@ -1,4 +1,4 @@ -markTestSkipped('The test is run only when running against local chrome'); + $this->markTestSkipped('The test is run only when running against local firefox'); } } @@ -32,7 +32,7 @@ protected function tearDown(): void } } - public function testShouldStartAndStopServiceCreatedUsingShortcutConstructor() + public function testShouldStartAndStopServiceCreatedUsingShortcutConstructor(): void { // The createDefaultService() method expect path to the executable to be present in the environment variable putenv(FirefoxDriverService::WEBDRIVER_FIREFOX_DRIVER . '=' . getenv('GECKODRIVER_PATH')); @@ -52,7 +52,7 @@ public function testShouldStartAndStopServiceCreatedUsingShortcutConstructor() $this->assertInstanceOf(FirefoxDriverService::class, $this->driverService->stop()); } - public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor() + public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor(): void { $this->driverService = new FirefoxDriverService(getenv('GECKODRIVER_PATH'), 9515, ['-p=9515']); @@ -65,7 +65,7 @@ public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor() $this->assertFalse($this->driverService->isRunning()); } - public function testShouldUseDefaultExecutableIfNoneProvided() + public function testShouldUseDefaultExecutableIfNoneProvided(): void { // Put path where geckodriver binary is actually located to system PATH, to make sure we can locate it putenv('PATH=' . getenv('PATH') . ':' . dirname(getenv('GECKODRIVER_PATH'))); diff --git a/tests/functional/Firefox/FirefoxDriverTest.php b/tests/functional/Firefox/FirefoxDriverTest.php index ab6a03846..900746d2f 100644 --- a/tests/functional/Firefox/FirefoxDriverTest.php +++ b/tests/functional/Firefox/FirefoxDriverTest.php @@ -1,14 +1,18 @@ -markTestSkipped('The test is run only when running against local chrome'); + $this->markTestSkipped('The test is run only when running against local firefox'); } } @@ -33,7 +37,7 @@ protected function tearDown(): void } } - public function testShouldStartFirefoxDriver() + public function testShouldStartFirefoxDriver(): void { $this->startFirefoxDriver(); $this->assertInstanceOf(FirefoxDriver::class, $this->driver); @@ -49,12 +53,30 @@ public function testShouldStartFirefoxDriver() $this->assertSame('http://localhost:8000/', $this->driver->getCurrentURL()); } - private function startFirefoxDriver() + public function testShouldSetPreferenceWithFirefoxOptions(): void + { + $firefoxOptions = new FirefoxOptions(); + $firefoxOptions->setPreference('javascript.enabled', false); + + $this->startFirefoxDriver($firefoxOptions); + + $this->driver->get('http://localhost:8000/'); + + $noScriptElement = $this->driver->findElement(WebDriverBy::id('noscript')); + $this->assertEquals( + 'This element is only shown with JavaScript disabled.', + $noScriptElement->getText() + ); + } + + private function startFirefoxDriver(?FirefoxOptions $firefoxOptions = null): void { // The createDefaultService() method expect path to the executable to be present in the environment variable putenv(FirefoxDriverService::WEBDRIVER_FIREFOX_DRIVER . '=' . getenv('GECKODRIVER_PATH')); - $firefoxOptions = new FirefoxOptions(); + if ($firefoxOptions === null) { + $firefoxOptions = new FirefoxOptions(); + } $firefoxOptions->addArguments(['-headless']); $desiredCapabilities = DesiredCapabilities::firefox(); $desiredCapabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions); diff --git a/tests/functional/Firefox/FirefoxProfileTest.php b/tests/functional/Firefox/FirefoxProfileTest.php new file mode 100644 index 000000000..a8ff2c62d --- /dev/null +++ b/tests/functional/Firefox/FirefoxProfileTest.php @@ -0,0 +1,128 @@ +` element + * with some text at the end of each page Firefox renders. + * + * In case the extension will need to be modified, steps below must be followed, + * otherwise firefox won't load the modified extension: + * + * - Extract the xpi file (it is a zip archive) to some temporary directory + * - Make needed changes in the files + * - Install web-ext tool from Mozilla (@see https://github.com/mozilla/web-ext) + * - Sign in to https://addons.mozilla.org/cs/developers/addon/api/key/ to get your JWT API key and JWT secret + * - Run `web-ext sign --channel=unlisted --api-key=[you-api-key] --api-secret=[your-api-secret]` in the extension dir + * - Store the output file (`web-ext-artifacts/[...].xpi`) to the Fixtures/ directory + * + * @group exclude-saucelabs + * @covers \Facebook\WebDriver\Firefox\FirefoxProfile + */ +class FirefoxProfileTest extends TestCase +{ + /** @var FirefoxDriver */ + protected $driver; + + protected $firefoxTestExtensionFilename = __DIR__ . '/Fixtures/FirefoxExtension.xpi'; + + protected function setUp(): void + { + if (getenv('BROWSER_NAME') !== 'firefox' || empty(getenv('GECKODRIVER_PATH')) + || WebDriverTestCase::isSauceLabsBuild()) { + $this->markTestSkipped('The test is run only when running against local firefox'); + } + } + + protected function tearDown(): void + { + if ($this->driver instanceof RemoteWebDriver && $this->driver->getCommandExecutor() !== null) { + $this->driver->quit(); + } + } + + public function testShouldStartDriverWithEmptyProfile(): void + { + $firefoxProfile = new FirefoxProfile(); + $this->startFirefoxDriverWithProfile($firefoxProfile); + + $this->driver->get('http://localhost:8000/'); + $element = $this->driver->findElement(WebDriverBy::id('welcome')); + $this->assertSame( + 'Welcome to the php-webdriver testing page.', + $element->getText() + ); + } + + public function testShouldInstallExtension(): void + { + $firefoxProfile = new FirefoxProfile(); + $firefoxProfile->addExtension($this->firefoxTestExtensionFilename); + $this->startFirefoxDriverWithProfile($firefoxProfile); + + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); + + $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); + + // it sometimes takes split of a second for the extension to render the element, so we must use wait + $element = $this->driver->wait(5, 1)->until( + WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::id('webDriverExtensionTest')) + ); + + $this->assertEquals('This element was added by browser extension', $element->getText()); + } + + public function testShouldUseProfilePreferences(): void + { + $firefoxProfile = new FirefoxProfile(); + + // Please note, although it is possible to set preferences right into the profile (what this test does), + // we recommend using the setPreference() method on FirefoxOptions instead, so that you don't need to + // create FirefoxProfile. + $firefoxProfile->setPreference('javascript.enabled', false); + $this->assertSame('false', $firefoxProfile->getPreference('javascript.enabled')); + + $this->startFirefoxDriverWithProfile($firefoxProfile); + $this->driver->get('http://localhost:8000/'); + + $noScriptElement = $this->driver->findElement(WebDriverBy::id('noscript')); + $this->assertEquals( + 'This element is only shown with JavaScript disabled.', + $noScriptElement->getText() + ); + } + + protected function getTestPageUrl($path): string + { + $host = 'http://localhost:8000/'; + if ($alternateHost = getenv('FIXTURES_HOST')) { + $host = $alternateHost; + } + + return $host . '/' . $path; + } + + private function startFirefoxDriverWithProfile(FirefoxProfile $firefoxProfile): void + { + // The createDefaultService() method expect path to the executable to be present in the environment variable + putenv(FirefoxDriverService::WEBDRIVER_FIREFOX_DRIVER . '=' . getenv('GECKODRIVER_PATH')); + + $firefoxOptions = new FirefoxOptions(); + $firefoxOptions->addArguments(['-headless']); + $firefoxOptions->setProfile($firefoxProfile); + $desiredCapabilities = DesiredCapabilities::firefox(); + $desiredCapabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions); + + $this->driver = FirefoxDriver::start($desiredCapabilities); + } +} diff --git a/tests/functional/Firefox/Fixtures/FirefoxExtension.xpi b/tests/functional/Firefox/Fixtures/FirefoxExtension.xpi new file mode 100644 index 000000000..c5805fbeb Binary files /dev/null and b/tests/functional/Firefox/Fixtures/FirefoxExtension.xpi differ diff --git a/tests/functional/Remote/JsonWireCompatTest.php b/tests/functional/Remote/JsonWireCompatTest.php new file mode 100644 index 000000000..8dfb5e29a --- /dev/null +++ b/tests/functional/Remote/JsonWireCompatTest.php @@ -0,0 +1,17 @@ +expectException(UnexpectedResponseException::class); + $this->expectExceptionMessage('Unexpected server response for getting an element. Expected array'); + + JsonWireCompat::getElement(null); + } +} diff --git a/tests/functional/RemoteKeyboardTest.php b/tests/functional/RemoteKeyboardTest.php index 947c8b6f9..897d33cf7 100644 --- a/tests/functional/RemoteKeyboardTest.php +++ b/tests/functional/RemoteKeyboardTest.php @@ -1,4 +1,4 @@ -driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $this->driver->getKeyboard()->sendKeys('ab'); $this->driver->getKeyboard()->pressKey(WebDriverKeys::SHIFT); diff --git a/tests/functional/RemoteTargetLocatorTest.php b/tests/functional/RemoteTargetLocatorTest.php index a9e03e514..a3080e67a 100644 --- a/tests/functional/RemoteTargetLocatorTest.php +++ b/tests/functional/RemoteTargetLocatorTest.php @@ -1,7 +1,8 @@ -driver->get($this->getTestPageUrl('open_new_window.html')); + $this->driver->get($this->getTestPageUrl(TestPage::OPEN_NEW_WINDOW)); $originalWindowHandle = $this->driver->getWindowHandle(); $windowHandlesBefore = $this->driver->getWindowHandles(); @@ -23,7 +24,7 @@ public function testShouldSwitchToWindow() ); // At first the window should not be switched - $this->compatAssertStringContainsString('open_new_window.html', $this->driver->getCurrentURL()); + $this->assertStringContainsString('open_new_window.html', $this->driver->getCurrentURL()); $this->assertSame($originalWindowHandle, $this->driver->getWindowHandle()); /** @@ -43,13 +44,13 @@ public function testShouldSwitchToWindow() }); // After switchTo() is called, the active window should be changed - $this->compatAssertStringContainsString('index.html', $this->driver->getCurrentURL()); + $this->assertStringContainsString('index.html', $this->driver->getCurrentURL()); $this->assertNotSame($originalWindowHandle, $this->driver->getWindowHandle()); } - public function testActiveElement() + public function testActiveElement(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $activeElement = $this->driver->switchTo()->activeElement(); $this->assertInstanceOf(RemoteWebElement::class, $activeElement); @@ -61,75 +62,75 @@ public function testActiveElement() $this->assertSame('test_name', $activeElement->getAttribute('name')); } - public function testShouldSwitchToFrameByItsId() + public function testShouldSwitchToFrameByItsId(): void { $parentPage = 'This is the host page which contains an iFrame'; $firstChildFrame = 'This is the content of the iFrame'; $secondChildFrame = 'open new window'; - $this->driver->get($this->getTestPageUrl('page_with_frame.html')); + $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(0); - $this->compatAssertStringContainsString($firstChildFrame, $this->driver->getPageSource()); + $this->assertStringContainsString($firstChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->frame(null); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(1); - $this->compatAssertStringContainsString($secondChildFrame, $this->driver->getPageSource()); + $this->assertStringContainsString($secondChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->frame(null); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(0); - $this->compatAssertStringContainsString($firstChildFrame, $this->driver->getPageSource()); + $this->assertStringContainsString($firstChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->defaultContent(); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); } - public function testShouldSwitchToParentFrame() + public function testShouldSwitchToParentFrame(): void { $parentPage = 'This is the host page which contains an iFrame'; $firstChildFrame = 'This is the content of the iFrame'; - $this->driver->get($this->getTestPageUrl('page_with_frame.html')); + $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(0); - $this->compatAssertStringContainsString($firstChildFrame, $this->driver->getPageSource()); + $this->assertStringContainsString($firstChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->parent(); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); } - public function testShouldSwitchToFrameByElement() + public function testShouldSwitchToFrameByElement(): void { - $this->driver->get($this->getTestPageUrl('page_with_frame.html')); + $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); $element = $this->driver->findElement(WebDriverBy::id('iframe_content')); $this->driver->switchTo()->frame($element); - $this->compatAssertStringContainsString('This is the content of the iFrame', $this->driver->getPageSource()); + $this->assertStringContainsString('This is the content of the iFrame', $this->driver->getPageSource()); } /** * @group exclude-saucelabs */ - public function testShouldCreateNewWindow() + public function testShouldCreateNewWindow(): void { self::skipForJsonWireProtocol('Create new window is not supported in JsonWire protocol'); // Ensure that the initial context matches. $initialHandle = $this->driver->getWindowHandle(); - $this->driver->get($this->getTestPageUrl('index.html')); - $this->assertEquals($this->getTestPageUrl('index.html'), $this->driver->getCurrentUrl()); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); + $this->assertEquals($this->getTestPageUrl(TestPage::INDEX), $this->driver->getCurrentUrl()); $source = $this->driver->getPageSource(); - $this->compatAssertStringContainsString('

', $source); - $this->compatAssertStringContainsString('Welcome to the php-webdriver testing page.', $source); + $this->assertStringContainsString('

', $source); + $this->assertStringContainsString('Welcome to the php-webdriver testing page.', $source); $windowHandles = $this->driver->getWindowHandles(); $this->assertCount(1, $windowHandles); @@ -140,25 +141,25 @@ public function testShouldCreateNewWindow() $this->assertCount(2, $windowHandles); $newWindowHandle = $this->driver->getWindowHandle(); - $this->driver->get($this->getTestPageUrl('upload.html')); - $this->assertEquals($this->getTestPageUrl('upload.html'), $this->driver->getCurrentUrl()); + $this->driver->get($this->getTestPageUrl(TestPage::UPLOAD)); + $this->assertEquals($this->getTestPageUrl(TestPage::UPLOAD), $this->driver->getCurrentUrl()); $this->assertNotEquals($initialHandle, $newWindowHandle); // Switch back to original context. $this->driver->switchTo()->window($initialHandle); - $this->assertEquals($this->getTestPageUrl('index.html'), $this->driver->getCurrentUrl()); + $this->assertEquals($this->getTestPageUrl(TestPage::INDEX), $this->driver->getCurrentUrl()); } /** * @group exclude-saucelabs */ - public function testShouldNotAcceptStringAsFrameIdInW3cMode() + public function testShouldNotAcceptStringAsFrameIdInW3cMode(): void { self::skipForJsonWireProtocol(); - $this->driver->get($this->getTestPageUrl('page_with_frame.html')); + $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage( 'In W3C compliance mode frame must be either instance of WebDriverElement, integer or null' ); @@ -169,14 +170,14 @@ public function testShouldNotAcceptStringAsFrameIdInW3cMode() /** * @group exclude-saucelabs */ - public function testShouldAcceptStringAsFrameIdInJsonWireMode() + public function testShouldAcceptStringAsFrameIdInJsonWireMode(): void { self::skipForW3cProtocol(); - $this->driver->get($this->getTestPageUrl('page_with_frame.html')); + $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); $this->driver->switchTo()->frame('iframe_content'); - $this->compatAssertStringContainsString('This is the content of the iFrame', $this->driver->getPageSource()); + $this->assertStringContainsString('This is the content of the iFrame', $this->driver->getPageSource()); } } diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 6473e9b4a..a39a91070 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -1,19 +1,21 @@ -driver = RemoteWebDriver::create( $this->serverUrl, @@ -30,7 +32,7 @@ public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() $this->assertInstanceOf(HttpCommandExecutor::class, $this->driver->getCommandExecutor()); $this->assertNotEmpty($this->driver->getCommandExecutor()->getAddressOfRemoteServer()); - $this->assertTrue(is_string($this->driver->getSessionID())); + $this->assertIsString($this->driver->getSessionID()); $this->assertNotEmpty($this->driver->getSessionID()); $returnedCapabilities = $this->driver->getCapabilities(); @@ -49,7 +51,7 @@ public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() $this->assertNotEmpty($returnedCapabilities->getVersion()); } - public function testShouldAcceptCapabilitiesAsAnArray() + public function testShouldAcceptCapabilitiesAsAnArray(): void { // Method has a side-effect of converting whole content of desiredCapabilities to an array $this->desiredCapabilities->toArray(); @@ -64,7 +66,7 @@ public function testShouldAcceptCapabilitiesAsAnArray() $this->assertNotNull($this->driver->getCapabilities()); } - public function testShouldCreateWebDriverWithRequiredCapabilities() + public function testShouldCreateWebDriverWithRequiredCapabilities(): void { $requiredCapabilities = new DesiredCapabilities(); @@ -87,7 +89,7 @@ public function testShouldCreateWebDriverWithRequiredCapabilities() * However the the browser driver must be able to create non-headless instance (eg. inside xvfb). * @group exclude-saucelabs */ - public function testShouldCreateWebDriverWithoutCapabilities() + public function testShouldCreateWebDriverWithoutCapabilities(): void { if (getenv('GECKODRIVER') !== '1' && empty(getenv('CHROMEDRIVER_PATH'))) { $this->markTestSkipped('This test makes sense only when run directly via specific browser driver'); @@ -99,7 +101,7 @@ public function testShouldCreateWebDriverWithoutCapabilities() $this->assertNotEmpty($this->driver->getSessionID()); } - public function testShouldCreateInstanceFromExistingSessionId() + public function testShouldCreateInstanceFromExistingSessionId(): void { // Create driver instance and load page "index.html" $originalDriver = RemoteWebDriver::create( @@ -108,24 +110,66 @@ public function testShouldCreateInstanceFromExistingSessionId() $this->connectionTimeout, $this->requestTimeout ); - $originalDriver->get($this->getTestPageUrl('index.html')); - $this->compatAssertStringContainsString('/index.html', $originalDriver->getCurrentURL()); + $originalDriver->get($this->getTestPageUrl(TestPage::INDEX)); + $this->assertStringContainsString('/index.html', $originalDriver->getCurrentURL()); - // Store session ID + // Store session attributes $sessionId = $originalDriver->getSessionID(); $isW3cCompliant = $originalDriver->isW3cCompliant(); + $originalCapabilities = $originalDriver->getCapabilities(); + + $capabilitiesForSessionReuse = $originalCapabilities; + if ($this->isSeleniumServerUsed()) { + // do not provide capabilities when selenium server is used, to test they are read from selenium server + $capabilitiesForSessionReuse = null; + } // Create new RemoteWebDriver instance based on the session ID - $this->driver = RemoteWebDriver::createBySessionID($sessionId, $this->serverUrl, null, null, $isW3cCompliant); + $this->driver = RemoteWebDriver::createBySessionID( + $sessionId, + $this->serverUrl, + null, + null, + $isW3cCompliant, + $capabilitiesForSessionReuse + ); + + // Capabilities should be retrieved and be set to the driver instance + $returnedCapabilities = $this->driver->getCapabilities(); + $this->assertInstanceOf(WebDriverCapabilities::class, $returnedCapabilities); + + $expectedBrowserName = $this->desiredCapabilities->getBrowserName(); + + $this->assertEqualsIgnoringCase( + $expectedBrowserName, + $returnedCapabilities->getBrowserName() + ); + $this->assertEqualsCanonicalizing($originalCapabilities, $this->driver->getCapabilities()); // Check we reused the previous instance (window) and it has the same URL - $this->compatAssertStringContainsString('/index.html', $this->driver->getCurrentURL()); + $this->assertStringContainsString('/index.html', $this->driver->getCurrentURL()); // Do some interaction with the new driver $this->assertNotEmpty($this->driver->findElement(WebDriverBy::id('id_test'))->getText()); } - protected function createWebDriver() + public function testShouldRequireCapabilitiesToBeSetToReuseExistingSession(): void + { + $this->expectException(UnexpectedResponseException::class); + $this->expectExceptionMessage( + 'Existing Capabilities were not provided, and they also cannot be read from Selenium Grid' + ); + + // Do not provide capabilities, they also cannot be retrieved from the Selenium Grid + RemoteWebDriver::createBySessionID( + 'sessionId', + 'http://localhost:332/', // nothing should be running there + null, + null + ); + } + + protected function createWebDriver(): void { } } diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index 5a7d18e70..f638aa527 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -1,4 +1,4 @@ -driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $this->expectException(NoSuchElementException::class); $this->driver->findElement(WebDriverBy::id('not_existing')); } - public function testShouldFindElementIfExistsOnAPage() + public function testShouldFindElementIfExistsOnAPage(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('id_test')); $this->assertInstanceOf(RemoteWebElement::class, $element); } - public function testShouldReturnEmptyArrayIfElementsCannotBeFound() + public function testShouldReturnEmptyArrayIfElementsCannotBeFound(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $elements = $this->driver->findElements(WebDriverBy::cssSelector('not_existing')); - $this->assertTrue(is_array($elements)); + $this->assertIsArray($elements); $this->assertCount(0, $elements); } - public function testShouldFindMultipleElements() + public function testShouldFindMultipleElements(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $elements = $this->driver->findElements(WebDriverBy::cssSelector('ul > li')); - $this->assertTrue(is_array($elements)); + $this->assertIsArray($elements); $this->assertCount(5, $elements); $this->assertContainsOnlyInstancesOf(RemoteWebElement::class, $elements); } @@ -52,13 +52,13 @@ public function testShouldFindMultipleElements() /** * @group exclude-saucelabs */ - public function testEscapeCssSelector() + public function testEscapeCssSelector(): void { self::skipForJsonWireProtocol( 'CSS selectors containing special characters are not supported by the legacy protocol' ); - $this->driver->get($this->getTestPageUrl('escape_css.html')); + $this->driver->get($this->getTestPageUrl(TestPage::ESCAPE_CSS)); $element = $this->driver->findElement(WebDriverBy::id('.fo\'oo')); $this->assertSame('Foo', $element->getText()); diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 25b01c40f..95a01b9b7 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -1,4 +1,4 @@ -driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $this->assertEquals( 'php-webdriver test page', @@ -27,9 +27,9 @@ public function testShouldGetPageTitle() * @covers ::get * @covers ::getCurrentURL */ - public function testShouldGetCurrentUrl() + public function testShouldGetCurrentUrl(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $this->assertStringEndsWith('/index.html', $this->driver->getCurrentURL()); } @@ -37,20 +37,20 @@ public function testShouldGetCurrentUrl() /** * @covers ::getPageSource */ - public function testShouldGetPageSource() + public function testShouldGetPageSource(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $source = $this->driver->getPageSource(); - $this->compatAssertStringContainsString('

', $source); - $this->compatAssertStringContainsString('Welcome to the php-webdriver testing page.', $source); + $this->assertStringContainsString('

', $source); + $this->assertStringContainsString('Welcome to the php-webdriver testing page.', $source); } /** * @covers ::getSessionID * @covers ::isW3cCompliant */ - public function testShouldGetSessionId() + public function testShouldGetSessionId(): void { // This tests is intentionally included in another test, to not slow down build. // @TODO Remove following in 2.0 @@ -62,7 +62,7 @@ public function testShouldGetSessionId() $sessionId = $this->driver->getSessionID(); - $this->assertTrue(is_string($sessionId)); + $this->assertIsString($sessionId); $this->assertNotEmpty($sessionId); } @@ -70,13 +70,13 @@ public function testShouldGetSessionId() * @group exclude-saucelabs * @covers ::getAllSessions */ - public function testShouldGetAllSessions() + public function testShouldGetAllSessions(): void { self::skipForW3cProtocol(); $sessions = RemoteWebDriver::getAllSessions($this->serverUrl, 30000); - $this->assertTrue(is_array($sessions)); + $this->assertIsArray($sessions); $this->assertCount(1, $sessions); $this->assertArrayHasKey('capabilities', $sessions[0]); @@ -89,7 +89,7 @@ public function testShouldGetAllSessions() * @covers ::getCommandExecutor * @covers ::quit */ - public function testShouldQuitAndUnsetExecutor() + public function testShouldQuitAndUnsetExecutor(): void { self::skipForW3cProtocol(); @@ -116,14 +116,14 @@ public function testShouldQuitAndUnsetExecutor() * @covers ::getWindowHandle * @covers ::getWindowHandles */ - public function testShouldGetWindowHandles() + public function testShouldGetWindowHandles(): void { - $this->driver->get($this->getTestPageUrl('open_new_window.html')); + $this->driver->get($this->getTestPageUrl(TestPage::OPEN_NEW_WINDOW)); $windowHandle = $this->driver->getWindowHandle(); $windowHandles = $this->driver->getWindowHandles(); - $this->assertTrue(is_string($windowHandle)); + $this->assertIsString($windowHandle); $this->assertNotEmpty($windowHandle); $this->assertSame([$windowHandle], $windowHandles); @@ -140,9 +140,9 @@ public function testShouldGetWindowHandles() /** * @covers ::close */ - public function testShouldCloseWindow() + public function testShouldCloseWindow(): void { - $this->driver->get($this->getTestPageUrl('open_new_window.html')); + $this->driver->get($this->getTestPageUrl(TestPage::OPEN_NEW_WINDOW)); $this->driver->findElement(WebDriverBy::cssSelector('a'))->click(); $this->driver->wait()->until(WebDriverExpectedCondition::numberOfWindowsToBe(2)); @@ -158,9 +158,9 @@ public function testShouldCloseWindow() * @covers ::executeScript * @group exclude-saucelabs */ - public function testShouldExecuteScriptAndDoNotBlockExecution() + public function testShouldExecuteScriptAndDoNotBlockExecution(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('id_test')); $this->assertSame('Test by ID', $element->getText()); @@ -188,11 +188,11 @@ function(){document.getElementById("id_test").innerHTML = "Text changed by scrip * @covers ::executeAsyncScript * @covers \Facebook\WebDriver\WebDriverTimeouts::setScriptTimeout */ - public function testShouldExecuteAsyncScriptAndWaitUntilItIsFinished() + public function testShouldExecuteAsyncScriptAndWaitUntilItIsFinished(): void { $this->driver->manage()->timeouts()->setScriptTimeout(1); - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('id_test')); $this->assertSame('Test by ID', $element->getText()); @@ -228,11 +228,11 @@ function(){ * @covers ::prepareScriptArguments * @group exclude-saucelabs */ - public function testShouldExecuteScriptWithParamsAndReturnValue() + public function testShouldExecuteScriptWithParamsAndReturnValue(): void { $this->driver->manage()->timeouts()->setScriptTimeout(1); - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element1 = $this->driver->findElement(WebDriverBy::id('id_test')); $element2 = $this->driver->findElement(WebDriverBy::className('test_class')); @@ -250,13 +250,14 @@ public function testShouldExecuteScriptWithParamsAndReturnValue() /** * @covers ::takeScreenshot + * @covers \Facebook\WebDriver\Support\ScreenshotHelper */ - public function testShouldTakeScreenshot() + public function testShouldTakeScreenshot(): void { if (!extension_loaded('gd')) { $this->markTestSkipped('GD extension must be enabled'); } - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $outputPng = $this->driver->takeScreenshot(); @@ -269,18 +270,19 @@ public function testShouldTakeScreenshot() /** * @covers ::takeScreenshot + * @covers \Facebook\WebDriver\Support\ScreenshotHelper * @group exclude-safari * Safari is returning different color profile and it does not have way to configure "force-color-profile" */ - public function testShouldSaveScreenshotToFile() + public function testShouldSaveScreenshotToFile(): void { if (!extension_loaded('gd')) { $this->markTestSkipped('GD extension must be enabled'); } - // Intentionally save screenshot to subdirectory to tests it is being created + $screenshotPath = sys_get_temp_dir() . '/' . uniqid('php-webdriver-') . '/selenium-screenshot.png'; - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $this->driver->takeScreenshot($screenshotPath); @@ -312,12 +314,12 @@ public function testShouldSaveScreenshotToFile() * @group exclude-saucelabs * Status endpoint is not supported on Sauce Labs */ - public function testShouldGetRemoteEndStatus() + public function testShouldGetRemoteEndStatus(): void { $status = $this->driver->getStatus(); - $this->assertTrue(is_bool($status->isReady())); - $this->assertTrue(is_array($status->getMeta())); + $this->assertIsBool($status->isReady()); + $this->assertIsArray($status->getMeta()); if (getenv('BROWSER_NAME') !== 'safari') { $this->assertNotEmpty($status->getMessage()); diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index f10c7487b..6a21cb36c 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -1,4 +1,4 @@ -driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $elementWithSimpleText = $this->driver->findElement(WebDriverBy::id('text-simple')); $elementWithTextWithSpaces = $this->driver->findElement(WebDriverBy::id('text-with-spaces')); @@ -33,38 +33,61 @@ public function testShouldGetText() /** * @covers ::getAttribute */ - public function testShouldGetAttributeValue() + public function testShouldGetAttributeValue(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('text-simple')); $this->assertSame('note', $element->getAttribute('role')); $this->assertSame('height: 5em; border: 1px solid black;', $element->getAttribute('style')); $this->assertSame('text-simple', $element->getAttribute('id')); + $this->assertNull($element->getAttribute('notExisting')); + } + + /** + * @covers ::getDomProperty + */ + public function testShouldGetDomPropertyValue(): void + { + self::skipForJsonWireProtocol(); + + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); + + $element = $this->driver->findElement(WebDriverBy::id('div-with-html')); + + $this->assertStringContainsString( + '

This div has some more html inside.

', + $element->getDomProperty('innerHTML') + ); + $this->assertSame('foo bar', $element->getDomProperty('className')); // IDL property + $this->assertSame('foo bar', $element->getAttribute('class')); // HTML attribute should be the same + $this->assertSame('DIV', $element->getDomProperty('tagName')); + $this->assertSame(2, $element->getDomProperty('childElementCount')); + $this->assertNull($element->getDomProperty('notExistingProperty')); } /** * @covers ::getLocation */ - public function testShouldGetLocation() + public function testShouldGetLocation(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('element-with-location')); $elementLocation = $element->getLocation(); $this->assertInstanceOf(WebDriverPoint::class, $elementLocation); $this->assertSame(33, $elementLocation->getX()); - $this->assertSame(500, $elementLocation->getY()); + $this->assertSame(550, $elementLocation->getY()); } /** * @covers ::getLocationOnScreenOnceScrolledIntoView */ - public function testShouldGetLocationOnScreenOnceScrolledIntoView() + public function testShouldGetLocationOnScreenOnceScrolledIntoView(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('element-out-of-viewport')); @@ -87,9 +110,9 @@ public function testShouldGetLocationOnScreenOnceScrolledIntoView() /** * @covers ::getSize */ - public function testShouldGetSize() + public function testShouldGetSize(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('element-with-location')); @@ -102,9 +125,9 @@ public function testShouldGetSize() /** * @covers ::getCSSValue */ - public function testShouldGetCssValue() + public function testShouldGetCssValue(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $elementWithBorder = $this->driver->findElement(WebDriverBy::id('text-simple')); $elementWithoutBorder = $this->driver->findElement(WebDriverBy::id('text-with-spaces')); @@ -122,9 +145,9 @@ public function testShouldGetCssValue() /** * @covers ::getTagName */ - public function testShouldGetTagName() + public function testShouldGetTagName(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $paragraphElement = $this->driver->findElement(WebDriverBy::id('id_test')); @@ -134,15 +157,15 @@ public function testShouldGetTagName() /** * @covers ::click */ - public function testShouldClick() + public function testShouldClick(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $linkElement = $this->driver->findElement(WebDriverBy::id('a-form')); $linkElement->click(); $this->driver->wait()->until( - WebDriverExpectedCondition::urlContains('form.html') + WebDriverExpectedCondition::urlContains(TestPage::FORM) ); $this->assertTrue(true); // To generate coverage, see https://github.com/sebastianbergmann/phpunit/issues/3016 @@ -157,7 +180,7 @@ public function testShouldClick() * @group exclude-chrome * @group exclude-edge */ - public function testGeckoDriverShouldClickOnBlockLevelElement() + public function testGeckoDriverShouldClickOnBlockLevelElement(): void { self::skipForUnmatchedBrowsers(['firefox']); @@ -169,7 +192,7 @@ public function testGeckoDriverShouldClickOnBlockLevelElement() ]; foreach ($links as $linkid) { - $this->driver->get($this->getTestPageUrl('gecko653.html')); + $this->driver->get($this->getTestPageUrl(TestPage::GECKO_653)); $linkElement = $this->driver->findElement(WebDriverBy::id($linkid)); $linkElement->click(); @@ -186,13 +209,14 @@ public function testGeckoDriverShouldClickOnBlockLevelElement() * @group exclude-chrome * @group exclude-edge */ - public function testGeckoDriverShouldClickNotInteractable() + public function testGeckoDriverShouldClickNotInteractable(): void { self::skipForUnmatchedBrowsers(['firefox']); - $this->driver->get($this->getTestPageUrl('gecko653.html')); + $this->driver->get($this->getTestPageUrl(TestPage::GECKO_653)); $linkElement = $this->driver->findElement(WebDriverBy::id('a-index-plain-hidden')); + try { $linkElement->click(); $this->fail('No exception was thrown when clicking an inaccessible link'); @@ -201,6 +225,7 @@ public function testGeckoDriverShouldClickNotInteractable() } $linkElement = $this->driver->findElement(WebDriverBy::id('a-index-hidden-block-child')); + try { $linkElement->click(); $this->fail('No exception was thrown when clicking an inaccessible link'); @@ -212,9 +237,9 @@ public function testGeckoDriverShouldClickNotInteractable() /** * @covers ::clear */ - public function testShouldClearFormElementText() + public function testShouldClearFormElementText(): void { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $input = $this->driver->findElement(WebDriverBy::id('input-text')); $textarea = $this->driver->findElement(WebDriverBy::id('textarea')); @@ -231,9 +256,9 @@ public function testShouldClearFormElementText() /** * @covers ::sendKeys */ - public function testShouldSendKeysToFormElement() + public function testShouldSendKeysToFormElement(): void { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $input = $this->driver->findElement(WebDriverBy::id('input-text')); $textarea = $this->driver->findElement(WebDriverBy::id('textarea')); @@ -264,12 +289,30 @@ public function testShouldSendKeysToFormElement() $this->assertSame('bat13 37', $textarea->getAttribute('value')); } + /** + * @covers ::isDisplayed + * @covers \Facebook\WebDriver\Remote\RemoteWebDriver::execute + * @covers \Facebook\WebDriver\Support\IsElementDisplayedAtom + */ + public function testShouldDetectElementDisplayedness(): void + { + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); + + $visibleElement = $this->driver->findElement(WebDriverBy::cssSelector('.test_class')); + $elementOutOfViewport = $this->driver->findElement(WebDriverBy::id('element-out-of-viewport')); + $hiddenElement = $this->driver->findElement(WebDriverBy::id('hidden-element')); + + $this->assertTrue($visibleElement->isDisplayed()); + $this->assertTrue($elementOutOfViewport->isDisplayed()); + $this->assertFalse($hiddenElement->isDisplayed()); + } + /** * @covers ::isEnabled */ - public function testShouldDetectEnabledInputs() + public function testShouldDetectEnabledInputs(): void { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $inputEnabled = $this->driver->findElement(WebDriverBy::id('input-text')); $inputDisabled = $this->driver->findElement(WebDriverBy::id('input-text-disabled')); @@ -281,9 +324,9 @@ public function testShouldDetectEnabledInputs() /** * @covers ::isSelected */ - public function testShouldSelectedInputsOrOptions() + public function testShouldSelectedInputsOrOptions(): void { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $checkboxSelected = $this->driver->findElement( WebDriverBy::cssSelector('input[name=checkbox][value=second]') @@ -309,9 +352,9 @@ public function testShouldSelectedInputsOrOptions() * @covers ::submit * @group exclude-edge */ - public function testShouldSubmitFormBySubmitEventOnForm() + public function testShouldSubmitFormBySubmitEventOnForm(): void { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $formElement = $this->driver->findElement(WebDriverBy::cssSelector('form')); @@ -327,9 +370,9 @@ public function testShouldSubmitFormBySubmitEventOnForm() /** * @covers ::submit */ - public function testShouldSubmitFormBySubmitEventOnFormInputElement() + public function testShouldSubmitFormBySubmitEventOnFormInputElement(): void { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $inputTextElement = $this->driver->findElement(WebDriverBy::id('input-text')); @@ -345,9 +388,9 @@ public function testShouldSubmitFormBySubmitEventOnFormInputElement() /** * @covers ::click */ - public function testShouldSubmitFormByClickOnSubmitInput() + public function testShouldSubmitFormByClickOnSubmitInput(): void { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $submitElement = $this->driver->findElement(WebDriverBy::id('submit')); @@ -363,9 +406,9 @@ public function testShouldSubmitFormByClickOnSubmitInput() /** * @covers ::equals */ - public function testShouldCompareEqualsElement() + public function testShouldCompareEqualsElement(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $firstElement = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); $differentElement = $this->driver->findElement(WebDriverBy::cssSelector('#text-simple')); @@ -382,18 +425,18 @@ public function testShouldCompareEqualsElement() /** * @covers ::findElement */ - public function testShouldThrowExceptionIfChildElementCannotBeFound() + public function testShouldThrowExceptionIfChildElementCannotBeFound(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); $this->expectException(NoSuchElementException::class); $element->findElement(WebDriverBy::id('not_existing')); } - public function testShouldFindChildElementIfExistsOnAPage() + public function testShouldFindChildElementIfExistsOnAPage(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); $childElement = $element->findElement(WebDriverBy::cssSelector('li')); @@ -403,26 +446,26 @@ public function testShouldFindChildElementIfExistsOnAPage() $this->assertSame('First', $childElement->getText()); } - public function testShouldReturnEmptyArrayIfChildElementsCannotBeFound() + public function testShouldReturnEmptyArrayIfChildElementsCannotBeFound(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); $childElements = $element->findElements(WebDriverBy::cssSelector('not_existing')); - $this->assertTrue(is_array($childElements)); + $this->assertIsArray($childElements); $this->assertCount(0, $childElements); } - public function testShouldFindMultipleChildElements() + public function testShouldFindMultipleChildElements(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); $allElements = $this->driver->findElements(WebDriverBy::cssSelector('li')); $childElements = $element->findElements(WebDriverBy::cssSelector('li')); - $this->assertTrue(is_array($childElements)); + $this->assertIsArray($childElements); $this->assertCount(5, $allElements); // there should be 5
  • elements on page $this->assertCount(3, $childElements); // but we should find only subelements of one
      $this->assertContainsOnlyInstancesOf(RemoteWebElement::class, $childElements); @@ -430,9 +473,10 @@ public function testShouldFindMultipleChildElements() /** * @covers ::takeElementScreenshot + * @covers \Facebook\WebDriver\Support\ScreenshotHelper * @group exclude-saucelabs */ - public function testShouldTakeAndSaveElementScreenshot() + public function testShouldTakeAndSaveElementScreenshot(): void { self::skipForJsonWireProtocol('Take element screenshot is only part of W3C protocol'); @@ -444,10 +488,9 @@ public function testShouldTakeAndSaveElementScreenshot() $isCi = (new CiDetector())->isCiDetected(); $isSafari = getenv('BROWSER_NAME') === 'safari'; - // Intentionally save screenshot to subdirectory to tests it is being created $screenshotPath = sys_get_temp_dir() . '/' . uniqid('php-webdriver-') . '/element-screenshot.png'; - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('red-box')); @@ -472,8 +515,11 @@ public function testShouldTakeAndSaveElementScreenshot() // Assert string output $imageFromString = imagecreatefromstring($outputPngString); - $this->assertNotFalse($imageFromString); - $this->assertTrue(is_resource($imageFromString)); + if (version_compare(phpversion(), '8.0.0', '>=')) { + $this->assertInstanceOf(\GdImage::class, $imageFromString); + } else { + $this->assertTrue(is_resource($imageFromString)); + } if ($isSafari && !$isCi) { $this->assertEquals(10, imagesx($imageFromString)); diff --git a/tests/functional/ReportSauceLabsStatusListener.php b/tests/functional/ReportSauceLabsStatusListener.php index 7c3bed873..1a9077807 100644 --- a/tests/functional/ReportSauceLabsStatusListener.php +++ b/tests/functional/ReportSauceLabsStatusListener.php @@ -1,4 +1,4 @@ -retrieveLoggerEvents(WebDriverBy::id('keyboardEventsLog')); } - /** - * @return array - */ - private function retrieveLoggedMouseEvents() + private function retrieveLoggedMouseEvents(): array { return $this->retrieveLoggerEvents(WebDriverBy::id('mouseEventsLog')); } diff --git a/tests/functional/ShadowDomTest.php b/tests/functional/ShadowDomTest.php new file mode 100644 index 000000000..dc95eaa84 --- /dev/null +++ b/tests/functional/ShadowDomTest.php @@ -0,0 +1,75 @@ +driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); + + $element = $this->driver->findElement(WebDriverBy::cssSelector('custom-checkbox-element')); + + $shadowRoot = $element->getShadowRoot(); + + $this->assertInstanceOf(ShadowRoot::class, $shadowRoot); + } + + public function testShouldThrowExceptionWhenGettingShadowRootWithElementNotHavingShadowRoot(): void + { + $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); + + $element = $this->driver->findElement(WebDriverBy::cssSelector('#no-shadow-root')); + + $this->expectException(NoSuchShadowRootException::class); + $element->getShadowRoot(); + } + + public function testShouldFindElementUnderShadowRoot(): void + { + $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); + + $element = $this->driver->findElement(WebDriverBy::cssSelector('custom-checkbox-element')); + + $shadowRoot = $element->getShadowRoot(); + + $elementInShadow = $shadowRoot->findElement(WebDriverBy::cssSelector('input')); + $this->assertSame('checkbox', $elementInShadow->getAttribute('type')); + + $elementsInShadow = $shadowRoot->findElements(WebDriverBy::cssSelector('div')); + $this->assertCount(2, $elementsInShadow); + } + + public function testShouldReferenceTheSameShadowRootAsFromExecuteScript(): void + { + $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); + + $element = $this->driver->findElement(WebDriverBy::cssSelector('custom-checkbox-element')); + + /** @var WebDriverElement $elementFromScript */ + $elementFromScript = $this->driver->executeScript( + 'return arguments[0].shadowRoot;', + [$element] + ); + + $shadowRoot = $element->getShadowRoot(); + + $this->assertSame($shadowRoot->getId(), reset($elementFromScript)); + } +} diff --git a/tests/functional/TestPage.php b/tests/functional/TestPage.php new file mode 100644 index 000000000..92629ed54 --- /dev/null +++ b/tests/functional/TestPage.php @@ -0,0 +1,25 @@ +driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $element = $this->driver->findElement(WebDriverBy::id('item-1')); @@ -47,9 +47,9 @@ public function testShouldClickOnElement() $this->assertSame($logs, $loggedEvents); } - public function testShouldClickAndHoldOnElementAndRelease() + public function testShouldClickAndHoldOnElementAndRelease(): void { - $this->driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $element = $this->driver->findElement(WebDriverBy::id('item-1')); @@ -72,13 +72,13 @@ public function testShouldClickAndHoldOnElementAndRelease() /** * @group exclude-saucelabs */ - public function testShouldContextClickOnElement() + public function testShouldContextClickOnElement(): void { if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::MICROSOFT_EDGE) { $this->markTestSkipped('Getting stuck in EdgeDriver'); } - $this->driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $element = $this->driver->findElement(WebDriverBy::id('item-2')); @@ -97,9 +97,9 @@ public function testShouldContextClickOnElement() * @group exclude-safari * https://github.com/webdriverio/webdriverio/issues/231 */ - public function testShouldDoubleClickOnElement() + public function testShouldDoubleClickOnElement(): void { - $this->driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $element = $this->driver->findElement(WebDriverBy::id('item-3')); @@ -113,9 +113,9 @@ public function testShouldDoubleClickOnElement() /** * @group exclude-saucelabs */ - public function testShouldSendKeysUpAndDown() + public function testShouldSendKeysUpAndDown(): void { - $this->driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $this->driver->action() ->keyDown(null, WebDriverKeys::CONTROL) @@ -142,9 +142,9 @@ public function testShouldSendKeysUpAndDown() * @group exclude-safari * https://developer.apple.com/forums/thread/662677 */ - public function testShouldMoveToElement() + public function testShouldMoveToElement(): void { - $this->driver->get($this->getTestPageUrl('sortable.html')); + $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); $item24 = $this->driver->findElement(WebDriverBy::id('item-2-4')); @@ -165,9 +165,9 @@ public function testShouldMoveToElement() * @group exclude-safari * https://developer.apple.com/forums/thread/662677 */ - public function testShouldMoveByOffset() + public function testShouldMoveByOffset(): void { - $this->driver->get($this->getTestPageUrl('sortable.html')); + $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); @@ -184,13 +184,13 @@ public function testShouldMoveByOffset() } /** - * @group exclude-saucelabs * @group exclude-safari * https://developer.apple.com/forums/thread/662677 + * @group exclude-saucelabs */ - public function testShouldDragAndDrop() + public function testShouldDragAndDrop(): void { - $this->driver->get($this->getTestPageUrl('sortable.html')); + $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); $item24 = $this->driver->findElement(WebDriverBy::id('item-2-4')); @@ -221,9 +221,9 @@ public function testShouldDragAndDrop() * https://developer.apple.com/forums/thread/662677 * it does not work even with Python Selenium, looks like Safaridriver does not implements Interaction API */ - public function testShouldDragAndDropBy() + public function testShouldDragAndDropBy(): void { - $this->driver->get($this->getTestPageUrl('sortable.html')); + $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); @@ -250,10 +250,7 @@ public function testShouldDragAndDropBy() ); } - /** - * @return array - */ - private function retrieveListContent() + private function retrieveListContent(): array { return [ $this->retrieveLoggerEvents(WebDriverBy::cssSelector('#sortable1')), diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php index e75533b8d..0d2e92a44 100644 --- a/tests/functional/WebDriverAlertTest.php +++ b/tests/functional/WebDriverAlertTest.php @@ -1,4 +1,4 @@ -driver->get($this->getTestPageUrl('alert.html')); + $this->driver->get($this->getTestPageUrl(TestPage::ALERT)); } - public function testShouldAcceptAlert() + public function testShouldAcceptAlert(): void { // Open alert (it is delayed for 1 second, to make sure following wait for alertIsPresent works properly) $this->driver->findElement(WebDriverBy::id('open-alert-delayed'))->click(); @@ -39,7 +39,7 @@ public function testShouldAcceptAlert() $this->driver->switchTo()->alert()->accept(); } - public function testShouldAcceptAndDismissConfirmation() + public function testShouldAcceptAndDismissConfirmation(): void { // Open confirmation $this->driver->findElement(WebDriverBy::id('open-confirm'))->click(); @@ -61,7 +61,7 @@ public function testShouldAcceptAndDismissConfirmation() $this->assertSame('dismissed', $this->getResultText()); } - public function testShouldSubmitPromptText() + public function testShouldSubmitPromptText(): void { // Open confirmation $this->driver->findElement(WebDriverBy::id('open-prompt'))->click(); @@ -77,7 +77,7 @@ public function testShouldSubmitPromptText() $this->assertSame('Text entered to prompt', $this->getResultText()); } - private function getResultText() + private function getResultText(): string { return $this->driver ->findElement(WebDriverBy::id('result')) diff --git a/tests/functional/WebDriverByTest.php b/tests/functional/WebDriverByTest.php index 6ff0c8a55..2aadf8de9 100644 --- a/tests/functional/WebDriverByTest.php +++ b/tests/functional/WebDriverByTest.php @@ -1,4 +1,4 @@ -driver->get($this->getTestPageUrl('index.html')); + string $webDriverByLocatorMethod, + string $webDriverByLocatorValue, + ?string $expectedText = null, + ?string $expectedAttributeValue = null + ): void { + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $by = call_user_func([WebDriverBy::class, $webDriverByLocatorMethod], $webDriverByLocatorValue); $element = $this->driver->findElement($by); @@ -42,7 +38,7 @@ public function testShouldFindTextElementByLocator( /** * @return array[] */ - public function provideTextElements() + public function provideTextElements(): array { return [ 'id' => ['id', 'id_test', 'Test by ID'], diff --git a/tests/functional/WebDriverCheckboxesTest.php b/tests/functional/WebDriverCheckboxesTest.php index e42ab144b..64372a4c8 100644 --- a/tests/functional/WebDriverCheckboxesTest.php +++ b/tests/functional/WebDriverCheckboxesTest.php @@ -1,4 +1,4 @@ -driver->get($this->getTestPageUrl('form_checkbox_radio.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM_CHECKBOX_RADIO)); } - public function testIsMultiple() + public function testIsMultiple(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -27,7 +27,7 @@ public function testIsMultiple() $this->assertTrue($checkboxes->isMultiple()); } - public function testGetOptions() + public function testGetOptions(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//form[2]//input[@type="checkbox"]')) @@ -36,7 +36,7 @@ public function testGetOptions() $this->assertNotEmpty($checkboxes->getOptions()); } - public function testGetFirstSelectedOption() + public function testGetFirstSelectedOption(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -47,7 +47,7 @@ public function testGetFirstSelectedOption() $this->assertSame('j2a', $checkboxes->getFirstSelectedOption()->getAttribute('value')); } - public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentForm() + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentForm(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@id="j5b"]')) @@ -56,7 +56,7 @@ public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociate $this->assertEquals('j5b', $checkboxes->getFirstSelectedOption()->getAttribute('value')); } - public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentFormWithoutId() + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentFormWithoutId(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@id="j5d"]')) @@ -65,7 +65,7 @@ public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociate $this->assertEquals('j5c', $checkboxes->getFirstSelectedOption()->getAttribute('value')); } - public function testSelectByValue() + public function testSelectByValue(): void { $selectedOptions = ['j2b', 'j2c']; @@ -83,7 +83,7 @@ public function testSelectByValue() $this->assertSame($selectedOptions, $selectedValues); } - public function testSelectByValueInvalid() + public function testSelectByValueInvalid(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -94,7 +94,7 @@ public function testSelectByValueInvalid() $checkboxes->selectByValue('notexist'); } - public function testSelectByIndex() + public function testSelectByIndex(): void { $selectedOptions = [1 => 'j2b', 2 => 'j2c']; @@ -112,7 +112,7 @@ public function testSelectByIndex() $this->assertSame(array_values($selectedOptions), $selectedValues); } - public function testSelectByIndexInvalid() + public function testSelectByIndexInvalid(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -125,11 +125,8 @@ public function testSelectByIndexInvalid() /** * @dataProvider provideSelectByVisibleTextData - * - * @param string $text - * @param string $value */ - public function testSelectByVisibleText($text, $value) + public function testSelectByVisibleText(string $text, string $value): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -143,7 +140,7 @@ public function testSelectByVisibleText($text, $value) /** * @return array[] */ - public function provideSelectByVisibleTextData() + public function provideSelectByVisibleTextData(): array { return [ ['J 2 B', 'j2b'], @@ -153,11 +150,8 @@ public function provideSelectByVisibleTextData() /** * @dataProvider provideSelectByVisiblePartialTextData - * - * @param string $text - * @param string $value */ - public function testSelectByVisiblePartialText($text, $value) + public function testSelectByVisiblePartialText(string $text, string $value): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -171,7 +165,7 @@ public function testSelectByVisiblePartialText($text, $value) /** * @return array[] */ - public function provideSelectByVisiblePartialTextData() + public function provideSelectByVisiblePartialTextData(): array { return [ ['2 B', 'j2b'], @@ -179,7 +173,7 @@ public function provideSelectByVisiblePartialTextData() ]; } - public function testDeselectAll() + public function testDeselectAll(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -191,7 +185,7 @@ public function testDeselectAll() $this->assertEmpty($checkboxes->getAllSelectedOptions()); } - public function testDeselectByIndex() + public function testDeselectByIndex(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -203,7 +197,7 @@ public function testDeselectByIndex() $this->assertEmpty($checkboxes->getAllSelectedOptions()); } - public function testDeselectByValue() + public function testDeselectByValue(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -215,7 +209,7 @@ public function testDeselectByValue() $this->assertEmpty($checkboxes->getAllSelectedOptions()); } - public function testDeselectByVisibleText() + public function testDeselectByVisibleText(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -227,7 +221,7 @@ public function testDeselectByVisibleText() $this->assertEmpty($checkboxes->getAllSelectedOptions()); } - public function testDeselectByVisiblePartialText() + public function testDeselectByVisiblePartialText(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) diff --git a/tests/functional/WebDriverNavigationTest.php b/tests/functional/WebDriverNavigationTest.php index 630656ea4..400ef86f0 100644 --- a/tests/functional/WebDriverNavigationTest.php +++ b/tests/functional/WebDriverNavigationTest.php @@ -1,4 +1,4 @@ -driver->navigate()->to($this->getTestPageUrl('index.html')); + $this->driver->navigate()->to($this->getTestPageUrl(TestPage::INDEX)); $this->assertStringEndsWith('/index.html', $this->driver->getCurrentURL()); } @@ -22,27 +22,27 @@ public function testShouldNavigateToUrl() * @covers ::back * @covers ::forward */ - public function testShouldNavigateBackAndForward() + public function testShouldNavigateBackAndForward(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $linkElement = $this->driver->findElement(WebDriverBy::id('a-form')); $linkElement->click(); $this->driver->wait()->until( - WebDriverExpectedCondition::urlContains('form.html') + WebDriverExpectedCondition::urlContains(TestPage::FORM) ); $this->driver->navigate()->back(); $this->driver->wait()->until( - WebDriverExpectedCondition::urlContains('index.html') + WebDriverExpectedCondition::urlContains(TestPage::INDEX) ); $this->driver->navigate()->forward(); $this->driver->wait()->until( - WebDriverExpectedCondition::urlContains('form.html') + WebDriverExpectedCondition::urlContains(TestPage::FORM) ); $this->assertTrue(true); // To generate coverage, see https://github.com/sebastianbergmann/phpunit/issues/3016 @@ -51,9 +51,9 @@ public function testShouldNavigateBackAndForward() /** * @covers ::refresh */ - public function testShouldRefreshPage() + public function testShouldRefreshPage(): void { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); // Change input element content, to make sure it was refreshed (=> cleared to original value) $inputElement = $this->driver->findElement(WebDriverBy::name('test_name')); diff --git a/tests/functional/WebDriverOptionsCookiesTest.php b/tests/functional/WebDriverOptionsCookiesTest.php index 2b0301256..a71ebafc0 100644 --- a/tests/functional/WebDriverOptionsCookiesTest.php +++ b/tests/functional/WebDriverOptionsCookiesTest.php @@ -1,4 +1,4 @@ -driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); } - public function testShouldSetGetAndDeleteCookies() + public function testShouldSetGetAndDeleteCookies(): void { $cookie1 = new Cookie('cookie1', 'cookie1Value'); $cookie2 = new Cookie('cookie2', 'cookie2Value'); diff --git a/tests/functional/WebDriverRadiosTest.php b/tests/functional/WebDriverRadiosTest.php index b6f900d8a..f35d37a7c 100644 --- a/tests/functional/WebDriverRadiosTest.php +++ b/tests/functional/WebDriverRadiosTest.php @@ -1,4 +1,4 @@ -driver->get($this->getTestPageUrl('form_checkbox_radio.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM_CHECKBOX_RADIO)); } - public function testIsMultiple() + public function testIsMultiple(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $this->assertFalse($radios->isMultiple()); } - public function testGetOptions() + public function testGetOptions(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $values = []; @@ -37,7 +37,7 @@ public function testGetOptions() $this->assertSame(['j3a', 'j3b', 'j3c'], $values); } - public function testGetFirstSelectedOption() + public function testGetFirstSelectedOption(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -46,14 +46,14 @@ public function testGetFirstSelectedOption() $this->assertSame('j3a', $radios->getFirstSelectedOption()->getAttribute('value')); } - public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentForm() + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentForm(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@id="j4b"]'))); $this->assertEquals('j4b', $radios->getFirstSelectedOption()->getAttribute('value')); } - public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentFormWithoutId() + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentFormWithoutId(): void { $radios = new WebDriverRadios( $this->driver->findElement(WebDriverBy::xpath('//input[@id="j4c"]')) @@ -62,7 +62,7 @@ public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociate $this->assertEquals('j4c', $radios->getFirstSelectedOption()->getAttribute('value')); } - public function testSelectByValue() + public function testSelectByValue(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $radios->selectByValue('j3b'); @@ -73,7 +73,7 @@ public function testSelectByValue() $this->assertSame('j3b', $selectedOptions[0]->getAttribute('value')); } - public function testSelectByValueInvalid() + public function testSelectByValueInvalid(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -82,7 +82,7 @@ public function testSelectByValueInvalid() $radios->selectByValue('notexist'); } - public function testSelectByIndex() + public function testSelectByIndex(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $radios->selectByIndex(1); @@ -92,7 +92,7 @@ public function testSelectByIndex() $this->assertSame('j3b', $allSelectedOptions[0]->getAttribute('value')); } - public function testSelectByIndexInvalid() + public function testSelectByIndexInvalid(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -103,11 +103,8 @@ public function testSelectByIndexInvalid() /** * @dataProvider provideSelectByVisibleTextData - * - * @param string $text - * @param string $value */ - public function testSelectByVisibleText($text, $value) + public function testSelectByVisibleText(string $text, string $value): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $radios->selectByVisibleText($text); @@ -117,7 +114,7 @@ public function testSelectByVisibleText($text, $value) /** * @return array[] */ - public function provideSelectByVisibleTextData() + public function provideSelectByVisibleTextData(): array { return [ ['J 3 B', 'j3b'], @@ -127,11 +124,8 @@ public function provideSelectByVisibleTextData() /** * @dataProvider provideSelectByVisiblePartialTextData - * - * @param string $text - * @param string $value */ - public function testSelectByVisiblePartialText($text, $value) + public function testSelectByVisiblePartialText(string $text, string $value): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $radios->selectByVisiblePartialText($text); @@ -141,7 +135,7 @@ public function testSelectByVisiblePartialText($text, $value) /** * @return array[] */ - public function provideSelectByVisiblePartialTextData() + public function provideSelectByVisiblePartialTextData(): array { return [ ['3 B', 'j3b'], @@ -149,7 +143,7 @@ public function provideSelectByVisiblePartialTextData() ]; } - public function testDeselectAllRadio() + public function testDeselectAllRadio(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -158,7 +152,7 @@ public function testDeselectAllRadio() $radios->deselectAll(); } - public function testDeselectByIndexRadio() + public function testDeselectByIndexRadio(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -167,7 +161,7 @@ public function testDeselectByIndexRadio() $radios->deselectByIndex(0); } - public function testDeselectByValueRadio() + public function testDeselectByValueRadio(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -176,7 +170,7 @@ public function testDeselectByValueRadio() $radios->deselectByValue('val'); } - public function testDeselectByVisibleTextRadio() + public function testDeselectByVisibleTextRadio(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -185,7 +179,7 @@ public function testDeselectByVisibleTextRadio() $radios->deselectByVisibleText('AB'); } - public function testDeselectByVisiblePartialTextRadio() + public function testDeselectByVisiblePartialTextRadio(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); diff --git a/tests/functional/WebDriverSelectTest.php b/tests/functional/WebDriverSelectTest.php index aa3cce1f2..a5640b1be 100644 --- a/tests/functional/WebDriverSelectTest.php +++ b/tests/functional/WebDriverSelectTest.php @@ -1,4 +1,4 @@ -driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); } - public function testShouldCreateNewInstanceForSelectElementAndDetectIfItIsMultiple() + /** + * @dataProvider multipleSelectDataProvider + */ + public function testShouldCreateNewInstanceForSelectElementAndDetectIfItIsMultiple(string $selector): void { $originalElement = $this->driver->findElement(WebDriverBy::cssSelector('#select')); - $originalMultipleElement = $this->driver->findElement(WebDriverBy::cssSelector('#select-multiple')); + $originalMultipleElement = $this->driver->findElement(WebDriverBy::cssSelector($selector)); $select = new WebDriverSelect($originalElement); $selectMultiple = new WebDriverSelect($originalMultipleElement); @@ -35,7 +38,16 @@ public function testShouldCreateNewInstanceForSelectElementAndDetectIfItIsMultip $this->assertTrue($selectMultiple->isMultiple()); } - public function testShouldThrowExceptionWhenNotInstantiatedOnSelectElement() + public static function multipleSelectDataProvider(): array + { + return [ + ['#select-multiple'], + ['#select-multiple-2'], + ['#select-multiple-3'], + ]; + } + + public function testShouldThrowExceptionWhenNotInstantiatedOnSelectElement(): void { $notSelectElement = $this->driver->findElement(WebDriverBy::cssSelector('textarea')); @@ -46,9 +58,8 @@ public function testShouldThrowExceptionWhenNotInstantiatedOnSelectElement() /** * @dataProvider provideSelectSelector - * @param string $selector */ - public function testShouldGetOptionsOfSelect($selector) + public function testShouldGetOptionsOfSelect(string $selector): void { $originalElement = $this->driver->findElement(WebDriverBy::cssSelector($selector)); $select = new WebDriverSelect($originalElement); @@ -62,7 +73,7 @@ public function testShouldGetOptionsOfSelect($selector) /** * @return array[] */ - public function provideSelectSelector() + public function provideSelectSelector(): array { return [ 'simple + + + +
      diff --git a/tests/functional/web/index.html b/tests/functional/web/index.html index 6107856ed..6202e3e9b 100644 --- a/tests/functional/web/index.html +++ b/tests/functional/web/index.html @@ -15,7 +15,6 @@ -

      Welcome to the php-webdriver testing page.

      @@ -37,6 +36,8 @@

      Welcome to the php-webdriver testing page.

      Events | Sortable + | + WebComponents

      Test by ID

      Test by Class

      @@ -60,7 +61,16 @@

      Welcome to the php-webdriver testing page.

      stripped

      -
      +
      +

      This div has some more html inside.

      +
      +
      + + + +
      Foo
      @@ -68,5 +78,9 @@

      Welcome to the php-webdriver testing page.

      Bar
      + + diff --git a/tests/functional/web/slow_pixel.png.php b/tests/functional/web/slow_pixel.png.php index d95805e21..06885539a 100644 --- a/tests/functional/web/slow_pixel.png.php +++ b/tests/functional/web/slow_pixel.png.php @@ -1,4 +1,4 @@ - diff --git a/tests/functional/web/upload.php b/tests/functional/web/upload.php index 71143d46c..92b52b57a 100644 --- a/tests/functional/web/upload.php +++ b/tests/functional/web/upload.php @@ -1,4 +1,4 @@ - diff --git a/tests/functional/web/web_components.html b/tests/functional/web/web_components.html new file mode 100644 index 000000000..65e458c30 --- /dev/null +++ b/tests/functional/web/web_components.html @@ -0,0 +1,38 @@ + + + + + WebComponents and Shadow DOM tests + + + + +

      WebComponents and Shadow DOM tests

      + +

      Element out of Shadow DOM

      +
      + +
      + + + + diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php index d58419a9a..444569499 100644 --- a/tests/unit/CookieTest.php +++ b/tests/unit/CookieTest.php @@ -1,15 +1,13 @@ -setPath('/bar'); @@ -33,9 +31,8 @@ public function testShouldSetAllProperties() /** * @depends testShouldSetAllProperties - * @param Cookie $cookie */ - public function testShouldBeConvertibleToArray(Cookie $cookie) + public function testShouldBeConvertibleToArray(Cookie $cookie): void { $this->assertSame( [ @@ -61,7 +58,7 @@ public function testShouldBeConvertibleToArray(Cookie $cookie) * https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol * https://w3c.github.io/webdriver/#add-cookie */ - public function testShouldNotContainNullValues() + public function testShouldNotContainNullValues(): void { $cookie = new Cookie('cookieName', 'someValue'); @@ -77,9 +74,8 @@ public function testShouldNotContainNullValues() /** * @depends testShouldSetAllProperties - * @param Cookie $cookie */ - public function testShouldProvideArrayAccessToProperties(Cookie $cookie) + public function testShouldProvideArrayAccessToProperties(Cookie $cookie): void { $this->assertSame('cookieName', $cookie['name']); $this->assertSame('someValue', $cookie['value']); @@ -96,7 +92,7 @@ public function testShouldProvideArrayAccessToProperties(Cookie $cookie) $this->assertArrayNotHasKey('domain', $cookie); } - public function testShouldBeCreatableFromAnArrayWithBasicValues() + public function testShouldBeCreatableFromAnArrayWithBasicValues(): void { $sourceArray = [ 'name' => 'cookieName', @@ -133,7 +129,7 @@ public function testShouldBeCreatableFromAnArrayWithBasicValues() $this->assertNull($cookie->getSameSite()); } - public function testShouldBeCreatableFromAnArrayWithAllValues() + public function testShouldBeCreatableFromAnArrayWithAllValues(): void { $sourceArray = [ 'name' => 'cookieName', @@ -160,15 +156,15 @@ public function testShouldBeCreatableFromAnArrayWithAllValues() /** * @dataProvider provideInvalidCookie - * @param string $name - * @param string $value - * @param string $domain - * @param string $expectedMessage */ - public function testShouldValidateCookieOnConstruction($name, $value, $domain, $expectedMessage) - { + public function testShouldValidateCookieOnConstruction( + ?string $name, + ?string $value, + ?string $domain, + ?string $expectedMessage + ): void { if ($expectedMessage) { - $this->expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage($expectedMessage); } @@ -183,7 +179,7 @@ public function testShouldValidateCookieOnConstruction($name, $value, $domain, $ /** * @return array[] */ - public function provideInvalidCookie() + public function provideInvalidCookie(): array { return [ // $name, $value, $domain, $expectedMessage diff --git a/tests/unit/Exception/DriverServerDiedExceptionTest.php b/tests/unit/Exception/Internal/DriverServerDiedExceptionTest.php similarity index 52% rename from tests/unit/Exception/DriverServerDiedExceptionTest.php rename to tests/unit/Exception/Internal/DriverServerDiedExceptionTest.php index 3c98e6e00..2e2fc5617 100644 --- a/tests/unit/Exception/DriverServerDiedExceptionTest.php +++ b/tests/unit/Exception/Internal/DriverServerDiedExceptionTest.php @@ -1,14 +1,14 @@ -assertSame('Error message ("/file/path.txt")', $exception->getMessage()); + } +} diff --git a/tests/unit/Exception/Internal/LogicExceptionTest.php b/tests/unit/Exception/Internal/LogicExceptionTest.php new file mode 100644 index 000000000..ba09d58d8 --- /dev/null +++ b/tests/unit/Exception/Internal/LogicExceptionTest.php @@ -0,0 +1,26 @@ +assertSame('Error message', $exception->getMessage()); + } + + public function testShouldCreateExceptionForInvalidHttpMethod(): void + { + $exception = LogicException::forInvalidHttpMethod('http://foo.bar/', 'FOO', ['key' => 'val']); + + $this->assertSame( + 'The http method called for "http://foo.bar/" is "FOO", but it has to be POST if you want to pass' + . ' the JSON params {"key":"val"}', + $exception->getMessage() + ); + } +} diff --git a/tests/unit/Exception/Internal/RuntimeExceptionTest.php b/tests/unit/Exception/Internal/RuntimeExceptionTest.php new file mode 100644 index 000000000..97adf87b3 --- /dev/null +++ b/tests/unit/Exception/Internal/RuntimeExceptionTest.php @@ -0,0 +1,34 @@ +assertSame('Error message', $exception->getMessage()); + } + + public function testShouldCreateExceptionForDriverError(): void + { + $processMock = $this->createConfiguredMock( + Process::class, + [ + 'getCommandLine' => '/bin/true --force', + 'getErrorOutput' => 'may the force be with you', + ] + ); + + $exception = RuntimeException::forDriverError($processMock); + + $this->assertSame( + 'Error starting driver executable "/bin/true --force": may the force be with you', + $exception->getMessage() + ); + } +} diff --git a/tests/unit/Exception/Internal/UnexpectedResponseExceptionTest.php b/tests/unit/Exception/Internal/UnexpectedResponseExceptionTest.php new file mode 100644 index 000000000..80f4b4a0d --- /dev/null +++ b/tests/unit/Exception/Internal/UnexpectedResponseExceptionTest.php @@ -0,0 +1,30 @@ +assertSame('Error message', $exception->getMessage()); + } + + public function testShouldCreateExceptionForJsonDecodingError(): void + { + $exception = UnexpectedResponseException::forJsonDecodingError(JSON_ERROR_SYNTAX, 'foo'); + + $this->assertSame( + <<getMessage() + ); + } +} diff --git a/tests/unit/Exception/Internal/WebDriverCurlExceptionTest.php b/tests/unit/Exception/Internal/WebDriverCurlExceptionTest.php new file mode 100644 index 000000000..acc94cb88 --- /dev/null +++ b/tests/unit/Exception/Internal/WebDriverCurlExceptionTest.php @@ -0,0 +1,34 @@ +assertSame( + <<getMessage() + ); + } + + public function provideParams(): array + { + return [ + 'null params' => [null, ''], + 'empty params' => [[], ''], + 'array of params' => [['bar' => 'foo', 'baz' => 'bat'], ' with params: {"bar":"foo","baz":"bat"}'], + ]; + } +} diff --git a/tests/unit/Exception/WebDriverExceptionTest.php b/tests/unit/Exception/WebDriverExceptionTest.php index 5013265f1..c770533d2 100644 --- a/tests/unit/Exception/WebDriverExceptionTest.php +++ b/tests/unit/Exception/WebDriverExceptionTest.php @@ -1,4 +1,4 @@ - */ + public const EXPECTED_DEFAULT_PREFS = [ FirefoxPreferences::READER_PARSE_ON_LOAD_ENABLED => false, FirefoxPreferences::DEVTOOLS_JSONVIEW => false, ]; - public function testShouldBeConstructedWithDefaultOptions() + public function testShouldBeConstructedWithDefaultOptions(): void { $options = new FirefoxOptions(); @@ -24,7 +25,7 @@ public function testShouldBeConstructedWithDefaultOptions() ); } - public function testShouldAddCustomOptions() + public function testShouldAddCustomOptions(): void { $options = new FirefoxOptions(); @@ -39,7 +40,7 @@ public function testShouldAddCustomOptions() ); } - public function testShouldOverwriteDefaultOptionsWhenSpecified() + public function testShouldOverwriteDefaultOptionsWhenSpecified(): void { $options = new FirefoxOptions(); @@ -56,7 +57,7 @@ public function testShouldOverwriteDefaultOptionsWhenSpecified() ); } - public function testShouldSetCustomPreference() + public function testShouldSetCustomPreference(): void { $options = new FirefoxOptions(); @@ -74,7 +75,7 @@ public function testShouldSetCustomPreference() ); } - public function testShouldAddArguments() + public function testShouldAddArguments(): void { $options = new FirefoxOptions(); @@ -89,7 +90,7 @@ public function testShouldAddArguments() ); } - public function testShouldJsonSerializeToArrayObject() + public function testShouldJsonSerializeToArrayObject(): void { $options = new FirefoxOptions(); $options->setOption('binary', '/usr/local/firefox/bin/firefox'); @@ -100,20 +101,20 @@ public function testShouldJsonSerializeToArrayObject() $this->assertSame('/usr/local/firefox/bin/firefox', $jsonSerialized['binary']); } - public function testShouldNotAllowToSetArgumentsOptionDirectly() + public function testShouldNotAllowToSetArgumentsOptionDirectly(): void { $options = new FirefoxOptions(); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Use addArguments() method to add Firefox arguments'); $options->setOption('args', []); } - public function testShouldNotAllowToSetPreferencesOptionDirectly() + public function testShouldNotAllowToSetPreferencesOptionDirectly(): void { $options = new FirefoxOptions(); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Use setPreference() method to set Firefox preferences'); $options->setOption('prefs', []); } diff --git a/tests/unit/Interactions/Internal/WebDriverButtonReleaseActionTest.php b/tests/unit/Interactions/Internal/WebDriverButtonReleaseActionTest.php index 22aed07ef..f14bad0c2 100644 --- a/tests/unit/Interactions/Internal/WebDriverButtonReleaseActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverButtonReleaseActionTest.php @@ -1,4 +1,4 @@ -getMockBuilder(WebDriverCoordinates::class) ->disableOriginalConstructor()->getMock(); diff --git a/tests/unit/Interactions/Internal/WebDriverClickActionTest.php b/tests/unit/Interactions/Internal/WebDriverClickActionTest.php index 7ec720f5d..de12aab26 100644 --- a/tests/unit/Interactions/Internal/WebDriverClickActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverClickActionTest.php @@ -1,4 +1,4 @@ -getMockBuilder(WebDriverCoordinates::class) ->disableOriginalConstructor()->getMock(); diff --git a/tests/unit/Interactions/Internal/WebDriverClickAndHoldActionTest.php b/tests/unit/Interactions/Internal/WebDriverClickAndHoldActionTest.php index 749215583..da5a18dee 100644 --- a/tests/unit/Interactions/Internal/WebDriverClickAndHoldActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverClickAndHoldActionTest.php @@ -1,4 +1,4 @@ -getMockBuilder(WebDriverCoordinates::class) ->disableOriginalConstructor()->getMock(); diff --git a/tests/unit/Interactions/Internal/WebDriverContextClickActionTest.php b/tests/unit/Interactions/Internal/WebDriverContextClickActionTest.php index 5bc3282e7..f3e66f6ee 100644 --- a/tests/unit/Interactions/Internal/WebDriverContextClickActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverContextClickActionTest.php @@ -1,4 +1,4 @@ -getMockBuilder(WebDriverCoordinates::class) ->disableOriginalConstructor()->getMock(); diff --git a/tests/unit/Interactions/Internal/WebDriverCoordinatesTest.php b/tests/unit/Interactions/Internal/WebDriverCoordinatesTest.php index b14ad2654..f8faee8d8 100644 --- a/tests/unit/Interactions/Internal/WebDriverCoordinatesTest.php +++ b/tests/unit/Interactions/Internal/WebDriverCoordinatesTest.php @@ -1,4 +1,4 @@ -getMockBuilder(WebDriverCoordinates::class) ->disableOriginalConstructor()->getMock(); diff --git a/tests/unit/Interactions/Internal/WebDriverKeyDownActionTest.php b/tests/unit/Interactions/Internal/WebDriverKeyDownActionTest.php index edf522b25..0fd8aa9dd 100644 --- a/tests/unit/Interactions/Internal/WebDriverKeyDownActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverKeyDownActionTest.php @@ -1,4 +1,4 @@ -createMock(WebDriverCoordinates::class); $this->webDriverMouse->expects($this->once())->method('click')->with($coords); diff --git a/tests/unit/Interactions/Internal/WebDriverKeyUpActionTest.php b/tests/unit/Interactions/Internal/WebDriverKeyUpActionTest.php index 275a4b968..f10d53b09 100644 --- a/tests/unit/Interactions/Internal/WebDriverKeyUpActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverKeyUpActionTest.php @@ -1,4 +1,4 @@ -createMock(WebDriverCoordinates::class); $this->webDriverMouse->expects($this->once())->method('click')->with($coords); diff --git a/tests/unit/Interactions/Internal/WebDriverMouseMoveActionTest.php b/tests/unit/Interactions/Internal/WebDriverMouseMoveActionTest.php index 47c8aa191..f23594852 100644 --- a/tests/unit/Interactions/Internal/WebDriverMouseMoveActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverMouseMoveActionTest.php @@ -1,4 +1,4 @@ -getMockBuilder(WebDriverCoordinates::class) ->disableOriginalConstructor()->getMock(); diff --git a/tests/unit/Interactions/Internal/WebDriverMouseToOffsetActionTest.php b/tests/unit/Interactions/Internal/WebDriverMouseToOffsetActionTest.php index af2c9be09..966b88325 100644 --- a/tests/unit/Interactions/Internal/WebDriverMouseToOffsetActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverMouseToOffsetActionTest.php @@ -1,4 +1,4 @@ -getMockBuilder(WebDriverCoordinates::class) ->disableOriginalConstructor()->getMock(); diff --git a/tests/unit/Interactions/Internal/WebDriverSendKeysActionTest.php b/tests/unit/Interactions/Internal/WebDriverSendKeysActionTest.php index d38816479..b610d083f 100644 --- a/tests/unit/Interactions/Internal/WebDriverSendKeysActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverSendKeysActionTest.php @@ -1,4 +1,4 @@ -getMockBuilder(WebDriverCoordinates::class) ->disableOriginalConstructor()->getMock(); diff --git a/tests/unit/Interactions/Internal/WebDriverSingleKeyActionTest.php b/tests/unit/Interactions/Internal/WebDriverSingleKeyActionTest.php index ddded93bb..9a3e61160 100644 --- a/tests/unit/Interactions/Internal/WebDriverSingleKeyActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverSingleKeyActionTest.php @@ -1,7 +1,8 @@ -expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage( 'keyDown / keyUp actions can only be used for modifier keys, but "foo" was given' ); diff --git a/tests/unit/Remote/CustomWebDriverCommandTest.php b/tests/unit/Remote/CustomWebDriverCommandTest.php index b56bc4aea..8e829c615 100644 --- a/tests/unit/Remote/CustomWebDriverCommandTest.php +++ b/tests/unit/Remote/CustomWebDriverCommandTest.php @@ -1,13 +1,13 @@ -assertSame('POST', $command->getCustomMethod()); } - public function testCustomCommandInvalidUrlExceptionInit() + public function testCustomCommandInvalidUrlExceptionInit(): void { - $this->expectException(WebDriverException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('URL of custom command has to start with / but is "url-without-leading-slash"'); new CustomWebDriverCommand('session-id-123', 'url-without-leading-slash', 'POST', []); } - public function testCustomCommandInvalidMethodExceptionInit() + public function testCustomCommandInvalidMethodExceptionInit(): void { - $this->expectException(WebDriverException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Invalid custom method "invalid-method", must be one of [GET, POST]'); new CustomWebDriverCommand('session-id-123', '/some-url', 'invalid-method', []); diff --git a/tests/unit/Remote/DesiredCapabilitiesTest.php b/tests/unit/Remote/DesiredCapabilitiesTest.php index 0d96831aa..e57b2a4d3 100644 --- a/tests/unit/Remote/DesiredCapabilitiesTest.php +++ b/tests/unit/Remote/DesiredCapabilitiesTest.php @@ -1,4 +1,4 @@ - 'fooVal', WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY] @@ -27,7 +27,7 @@ public function testShouldInstantiateWithCapabilitiesGivenInConstructor() ); } - public function testShouldInstantiateEmptyInstance() + public function testShouldInstantiateEmptyInstance(): void { $capabilities = new DesiredCapabilities(); @@ -35,7 +35,7 @@ public function testShouldInstantiateEmptyInstance() $this->assertSame([], $capabilities->toArray()); } - public function testShouldProvideAccessToCapabilitiesUsingSettersAndGetters() + public function testShouldProvideAccessToCapabilitiesUsingSettersAndGetters(): void { $capabilities = new DesiredCapabilities(); // generic capability setter @@ -51,7 +51,7 @@ public function testShouldProvideAccessToCapabilitiesUsingSettersAndGetters() $this->assertSame(333, $capabilities->getVersion()); } - public function testShouldAccessCapabilitiesIsser() + public function testShouldAccessCapabilitiesIsser(): void { $capabilities = new DesiredCapabilities(); @@ -66,7 +66,7 @@ public function testShouldAccessCapabilitiesIsser() $this->assertFalse($capabilities->is('customNull')); } - public function testShouldNotAllowToDisableJavascriptForNonHtmlUnitBrowser() + public function testShouldNotAllowToDisableJavascriptForNonHtmlUnitBrowser(): void { $this->expectException(\Exception::class); $this->expectExceptionMessage('isJavascriptEnabled() is a htmlunit-only option'); @@ -76,7 +76,7 @@ public function testShouldNotAllowToDisableJavascriptForNonHtmlUnitBrowser() $capabilities->setJavascriptEnabled(false); } - public function testShouldAllowToDisableJavascriptForHtmlUnitBrowser() + public function testShouldAllowToDisableJavascriptForHtmlUnitBrowser(): void { $capabilities = new DesiredCapabilities(); $capabilities->setBrowserName(WebDriverBrowserType::HTMLUNIT); @@ -87,15 +87,12 @@ public function testShouldAllowToDisableJavascriptForHtmlUnitBrowser() /** * @dataProvider provideBrowserCapabilities - * @param string $setupMethod - * @param string $expectedBrowser - * @param string $expectedPlatform */ public function testShouldProvideShortcutSetupForCapabilitiesOfEachBrowser( - $setupMethod, - $expectedBrowser, - $expectedPlatform - ) { + string $setupMethod, + string $expectedBrowser, + string $expectedPlatform + ): void { /** @var DesiredCapabilities $capabilities */ $capabilities = call_user_func([DesiredCapabilities::class, $setupMethod]); @@ -106,7 +103,7 @@ public function testShouldProvideShortcutSetupForCapabilitiesOfEachBrowser( /** * @return array[] */ - public function provideBrowserCapabilities() + public function provideBrowserCapabilities(): array { return [ ['android', WebDriverBrowserType::ANDROID, WebDriverPlatform::ANDROID], @@ -124,7 +121,7 @@ public function provideBrowserCapabilities() ]; } - public function testShouldSetupFirefoxWithDefaultOptions() + public function testShouldSetupFirefoxWithDefaultOptions(): void { $capabilitiesArray = DesiredCapabilities::firefox()->toArray(); @@ -137,7 +134,7 @@ public function testShouldSetupFirefoxWithDefaultOptions() ); } - public function testShouldSetupFirefoxWithCustomOptions() + public function testShouldSetupFirefoxWithCustomOptions(): void { $firefoxOptions = new FirefoxOptions(); $firefoxOptions->addArguments(['-headless']); @@ -159,7 +156,7 @@ public function testShouldSetupFirefoxWithCustomOptions() ); } - public function testShouldNotOverwriteDefaultFirefoxOptionsWhenAddingFirefoxOptionAsArray() + public function testShouldNotOverwriteDefaultFirefoxOptionsWhenAddingFirefoxOptionAsArray(): void { $capabilities = DesiredCapabilities::firefox(); $capabilities->setCapability('moz:firefoxOptions', ['args' => ['-headless']]); @@ -175,23 +172,21 @@ public function testShouldNotOverwriteDefaultFirefoxOptionsWhenAddingFirefoxOpti /** * @dataProvider provideW3cCapabilities - * @param DesiredCapabilities $inputJsonWireCapabilities - * @param array $expectedW3cCapabilities */ public function testShouldConvertCapabilitiesToW3cCompatible( DesiredCapabilities $inputJsonWireCapabilities, array $expectedW3cCapabilities - ) { + ): void { $this->assertJsonStringEqualsJsonString( - json_encode($expectedW3cCapabilities), - json_encode($inputJsonWireCapabilities->toW3cCompatibleArray()) + json_encode($expectedW3cCapabilities, JSON_THROW_ON_ERROR), + json_encode($inputJsonWireCapabilities->toW3cCompatibleArray(), JSON_THROW_ON_ERROR) ); } /** * @return array[] */ - public function provideW3cCapabilities() + public function provideW3cCapabilities(): array { $chromeOptions = new ChromeOptions(); $chromeOptions->addArguments(['--headless']); @@ -285,23 +280,6 @@ public function provideW3cCapabilities() ), ], ], - 'chromeOptions should be merged if already defined' => [ - new DesiredCapabilities([ - ChromeOptions::CAPABILITY => $chromeOptions, - ChromeOptions::CAPABILITY_W3C => [ - 'debuggerAddress' => '127.0.0.1:38947', - 'args' => ['window-size=1024,768'], - ], - ]), - [ - 'goog:chromeOptions' => new \ArrayObject( - [ - 'args' => ['--headless', 'window-size=1024,768'], - 'debuggerAddress' => '127.0.0.1:38947', - ] - ), - ], - ], 'firefox_profile should be converted' => [ new DesiredCapabilities([ FirefoxDriver::PROFILE => $firefoxProfileEncoded, diff --git a/tests/unit/Remote/HttpCommandExecutorTest.php b/tests/unit/Remote/HttpCommandExecutorTest.php index 30e117536..b29d788b1 100644 --- a/tests/unit/Remote/HttpCommandExecutorTest.php +++ b/tests/unit/Remote/HttpCommandExecutorTest.php @@ -1,4 +1,4 @@ -getFunctionMock(__NAMESPACE__, 'curl_setopt'); - $curlSetoptMock->expects($this->at(0)) - ->with($this->anything(), CURLOPT_URL, $expectedUrl); + bool $shouldResetExpectHeader, + string $expectedUrl, + ?string $expectedPostData + ): void { + $expectedCurlSetOptCalls = [ + [$this->anything(), CURLOPT_URL, $expectedUrl], + [$this->anything()], + ]; if ($shouldResetExpectHeader) { - $curlSetoptMock->expects($this->at(2)) - ->with( - $this->anything(), - CURLOPT_HTTPHEADER, - ['Content-Type: application/json;charset=UTF-8', 'Accept: application/json', 'Expect:'] - ); - $curlSetoptMock->expects($this->at(3)) - ->with($this->anything(), CURLOPT_POSTFIELDS, $expectedPostData); + $expectedCurlSetOptCalls[] = [ + $this->anything(), + CURLOPT_HTTPHEADER, + ['Content-Type: application/json;charset=UTF-8', 'Accept: application/json', 'Expect:'], + ]; } else { - $curlSetoptMock->expects($this->at(2)) - ->with( - $this->anything(), - CURLOPT_HTTPHEADER, - ['Content-Type: application/json;charset=UTF-8', 'Accept: application/json'] - ); - $curlSetoptMock->expects($this->at(3)) - ->with($this->anything(), CURLOPT_POSTFIELDS, $expectedPostData); + $expectedCurlSetOptCalls[] = [ + $this->anything(), + CURLOPT_HTTPHEADER, + ['Content-Type: application/json;charset=UTF-8', 'Accept: application/json'], + ]; } + $expectedCurlSetOptCalls[] = [$this->anything(), CURLOPT_POSTFIELDS, $expectedPostData]; + + $curlSetoptMock = $this->getFunctionMock(__NAMESPACE__, 'curl_setopt'); + $curlSetoptMock->expects($this->exactly(4)) + ->withConsecutive(...$expectedCurlSetOptCalls); + $curlExecMock = $this->getFunctionMock(__NAMESPACE__, 'curl_exec'); $curlExecMock->expects($this->once()) ->willReturn('{}'); @@ -64,7 +61,7 @@ public function testShouldSendRequestToAssembledUrl( /** * @return array[] */ - public function provideCommand() + public function provideCommand(): array { return [ 'POST command having :id placeholder in url' => [ diff --git a/tests/unit/Remote/LocalFileDetectorTest.php b/tests/unit/Remote/LocalFileDetectorTest.php index b58413d8b..dc6112636 100644 --- a/tests/unit/Remote/LocalFileDetectorTest.php +++ b/tests/unit/Remote/LocalFileDetectorTest.php @@ -1,4 +1,4 @@ -assertSame(__FILE__, $file); } - public function testShouldReturnNullIfFileNotDetected() + public function testShouldReturnNullIfFileNotDetected(): void { $detector = new LocalFileDetector(); diff --git a/tests/unit/Remote/RemoteWebDriverTest.php b/tests/unit/Remote/RemoteWebDriverTest.php index 7f0475dec..666ac9e5c 100644 --- a/tests/unit/Remote/RemoteWebDriverTest.php +++ b/tests/unit/Remote/RemoteWebDriverTest.php @@ -1,15 +1,17 @@ -driver = RemoteWebDriver::createBySessionID('session-id', 'http://foo.bar:4444/'); + // `createBySessionID()` is used because it is the simplest way to instantiate real RemoteWebDriver + $this->driver = RemoteWebDriver::createBySessionID( + 'session-id', + 'http://foo.bar:4444/', + null, + null, + true, + new DesiredCapabilities([]) + ); } /** * @covers ::manage */ - public function testShouldCreateWebDriverOptionsInstance() + public function testShouldCreateWebDriverOptionsInstance(): void { $wait = $this->driver->manage(); @@ -36,7 +46,7 @@ public function testShouldCreateWebDriverOptionsInstance() /** * @covers ::navigate */ - public function testShouldCreateWebDriverNavigationInstance() + public function testShouldCreateWebDriverNavigationInstance(): void { $wait = $this->driver->navigate(); @@ -46,7 +56,7 @@ public function testShouldCreateWebDriverNavigationInstance() /** * @covers ::switchTo */ - public function testShouldCreateRemoteTargetLocatorInstance() + public function testShouldCreateRemoteTargetLocatorInstance(): void { $wait = $this->driver->switchTo(); @@ -56,7 +66,7 @@ public function testShouldCreateRemoteTargetLocatorInstance() /** * @covers ::getMouse */ - public function testShouldCreateRemoteMouseInstance() + public function testShouldCreateRemoteMouseInstance(): void { $wait = $this->driver->getMouse(); @@ -66,7 +76,7 @@ public function testShouldCreateRemoteMouseInstance() /** * @covers ::getKeyboard */ - public function testShouldCreateRemoteKeyboardInstance() + public function testShouldCreateRemoteKeyboardInstance(): void { $wait = $this->driver->getKeyboard(); @@ -76,7 +86,7 @@ public function testShouldCreateRemoteKeyboardInstance() /** * @covers ::getTouch */ - public function testShouldCreateRemoteTouchScreenInstance() + public function testShouldCreateRemoteTouchScreenInstance(): void { $wait = $this->driver->getTouch(); @@ -86,7 +96,7 @@ public function testShouldCreateRemoteTouchScreenInstance() /** * @covers ::action */ - public function testShouldCreateWebDriverActionsInstance() + public function testShouldCreateWebDriverActionsInstance(): void { $wait = $this->driver->action(); @@ -96,10 +106,29 @@ public function testShouldCreateWebDriverActionsInstance() /** * @covers ::wait */ - public function testShouldCreateWebDriverWaitInstance() + public function testShouldCreateWebDriverWaitInstance(): void { $wait = $this->driver->wait(15, 1337); $this->assertInstanceOf(WebDriverWait::class, $wait); } + + /** + * @covers ::findElements + * @covers \Facebook\WebDriver\Exception\Internal\UnexpectedResponseException + */ + public function testShouldThrowExceptionOnUnexpectedValueFromRemoteEndWhenFindingElements(): void + { + $executorMock = $this->createMock(HttpCommandExecutor::class); + $executorMock->expects($this->once()) + ->method('execute') + ->with($this->isInstanceOf(WebDriverCommand::class)) + ->willReturn(new WebDriverResponse('session-id')); + + $this->driver->setCommandExecutor($executorMock); + + $this->expectException(UnexpectedResponseException::class); + $this->expectExceptionMessage('Server response to findElements command is not an array'); + $this->driver->findElements($this->createMock(WebDriverBy::class)); + } } diff --git a/tests/unit/Remote/RemoteWebElementTest.php b/tests/unit/Remote/RemoteWebElementTest.php index 7ca0eddb6..a7ec76d8a 100644 --- a/tests/unit/Remote/RemoteWebElementTest.php +++ b/tests/unit/Remote/RemoteWebElementTest.php @@ -1,4 +1,4 @@ -createMock(RemoteExecuteMethod::class); $element = new RemoteWebElement($executeMethod, 333); diff --git a/tests/unit/Remote/WebDriverCommandTest.php b/tests/unit/Remote/WebDriverCommandTest.php index 0bf6efb20..182b72d94 100644 --- a/tests/unit/Remote/WebDriverCommandTest.php +++ b/tests/unit/Remote/WebDriverCommandTest.php @@ -1,4 +1,4 @@ - 'bar']); @@ -14,4 +14,13 @@ public function testShouldSetOptionsUsingConstructor() $this->assertSame('bar-baz-name', $command->getName()); $this->assertSame(['foo' => 'bar'], $command->getParameters()); } + + public function testShouldCreateNewSessionCommand(): void + { + $command = WebDriverCommand::newSession(['bar' => 'baz']); + + $this->assertNull($command->getSessionID()); + $this->assertSame('newSession', $command->getName()); + $this->assertSame(['bar' => 'baz'], $command->getParameters()); + } } diff --git a/tests/unit/Support/ScreenshotHelperTest.php b/tests/unit/Support/ScreenshotHelperTest.php new file mode 100644 index 000000000..5f7ff0ca6 --- /dev/null +++ b/tests/unit/Support/ScreenshotHelperTest.php @@ -0,0 +1,103 @@ +assertDirectoryDoesNotExist($directoryPath); + + $executorMock = $this->createMock(RemoteExecuteMethod::class); + $executorMock->expects($this->once()) + ->method('execute') + ->with($this->equalTo(DriverCommand::SCREENSHOT)) + ->willReturn(self::BLACK_PIXEL); + + $helper = new ScreenshotHelper($executorMock); + $output = $helper->takePageScreenshot($fullFilePath); + + $this->assertSame(base64_decode(self::BLACK_PIXEL, true), $output); + + $this->assertDirectoryExists($directoryPath); + $this->assertFileExists($fullFilePath); + + unlink($fullFilePath); + rmdir($directoryPath); + } + + public function testShouldOnlyReturnBase64IfDirectoryNotProvided(): void + { + $executorMock = $this->createMock(RemoteExecuteMethod::class); + $executorMock->expects($this->once()) + ->method('execute') + ->with($this->equalTo(DriverCommand::SCREENSHOT)) + ->willReturn(self::BLACK_PIXEL); + + $helper = new ScreenshotHelper($executorMock); + $output = $helper->takePageScreenshot(); + + $this->assertSame(base64_decode(self::BLACK_PIXEL, true), $output); + } + + public function testShouldSaveElementScreenshotToSubdirectoryIfNotExists(): void + { + $fullFilePath = sys_get_temp_dir() . '/' . uniqid('php-webdriver-', true) . '/screenshot.png'; + $directoryPath = dirname($fullFilePath); + $this->assertDirectoryDoesNotExist($directoryPath); + $elementId = 'foo-id'; + + $executorMock = $this->createMock(RemoteExecuteMethod::class); + $executorMock->expects($this->once()) + ->method('execute') + ->with(DriverCommand::TAKE_ELEMENT_SCREENSHOT, [':id' => $elementId]) + ->willReturn(self::BLACK_PIXEL); + + $helper = new ScreenshotHelper($executorMock); + $output = $helper->takeElementScreenshot($elementId, $fullFilePath); + + $this->assertSame(base64_decode(self::BLACK_PIXEL, true), $output); + $this->assertDirectoryExists($directoryPath); + $this->assertFileExists($fullFilePath); + + unlink($fullFilePath); + rmdir($directoryPath); + } + + /** + * @dataProvider provideInvalidData + * @param mixed $data + */ + public function testShouldThrowExceptionWhenInvalidDataReceived($data, string $expectedExceptionMessage): void + { + $executorMock = $this->createMock(RemoteExecuteMethod::class); + $executorMock->expects($this->once()) + ->method('execute') + ->with($this->equalTo(DriverCommand::SCREENSHOT)) + ->willReturn($data); + + $helper = new ScreenshotHelper($executorMock); + + $this->expectException(UnexpectedResponseException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + $helper->takePageScreenshot(); + } + + public function provideInvalidData(): array + { + return [ + 'empty response' => [null, 'Error taking screenshot, no data received from the remote end'], + 'not valid base64 response' => ['invalid%base64', 'Error decoding screenshot data'], + ]; + } +} diff --git a/tests/unit/Support/XPathEscaperTest.php b/tests/unit/Support/XPathEscaperTest.php index e5a373f98..9804d93e1 100644 --- a/tests/unit/Support/XPathEscaperTest.php +++ b/tests/unit/Support/XPathEscaperTest.php @@ -1,4 +1,4 @@ - ['', "''"], diff --git a/tests/unit/WebDriverExpectedConditionTest.php b/tests/unit/WebDriverExpectedConditionTest.php index d401a8886..4ac75336b 100644 --- a/tests/unit/WebDriverExpectedConditionTest.php +++ b/tests/unit/WebDriverExpectedConditionTest.php @@ -1,4 +1,4 @@ -wait = new WebDriverWait($this->driverMock, 1, 1); } - public function testShouldDetectTitleIsCondition() + public function testShouldDetectTitleIsCondition(): void { $this->driverMock->expects($this->any()) ->method('getTitle') @@ -38,7 +38,7 @@ public function testShouldDetectTitleIsCondition() $this->assertTrue(call_user_func($condition->getApply(), $this->driverMock)); } - public function testShouldDetectTitleContainsCondition() + public function testShouldDetectTitleContainsCondition(): void { $this->driverMock->expects($this->any()) ->method('getTitle') @@ -51,7 +51,7 @@ public function testShouldDetectTitleContainsCondition() $this->assertTrue(call_user_func($condition->getApply(), $this->driverMock)); } - public function testShouldDetectTitleMatchesCondition() + public function testShouldDetectTitleMatchesCondition(): void { $this->driverMock->expects($this->any()) ->method('getTitle') @@ -64,7 +64,7 @@ public function testShouldDetectTitleMatchesCondition() $this->assertTrue(call_user_func($condition->getApply(), $this->driverMock)); } - public function testShouldDetectUrlIsCondition() + public function testShouldDetectUrlIsCondition(): void { $this->driverMock->expects($this->any()) ->method('getCurrentURL') @@ -77,7 +77,7 @@ public function testShouldDetectUrlIsCondition() $this->assertTrue(call_user_func($condition->getApply(), $this->driverMock)); } - public function testShouldDetectUrlContainsCondition() + public function testShouldDetectUrlContainsCondition(): void { $this->driverMock->expects($this->any()) ->method('getCurrentURL') @@ -90,7 +90,7 @@ public function testShouldDetectUrlContainsCondition() $this->assertTrue(call_user_func($condition->getApply(), $this->driverMock)); } - public function testShouldDetectUrlMatchesCondition() + public function testShouldDetectUrlMatchesCondition(): void { $this->driverMock->expects($this->any()) ->method('getCurrentURL') @@ -103,7 +103,7 @@ public function testShouldDetectUrlMatchesCondition() $this->assertTrue(call_user_func($condition->getApply(), $this->driverMock)); } - public function testShouldDetectPresenceOfElementLocatedCondition() + public function testShouldDetectPresenceOfElementLocatedCondition(): void { $element = new RemoteWebElement(new RemoteExecuteMethod($this->driverMock), 'id'); @@ -120,7 +120,7 @@ public function testShouldDetectPresenceOfElementLocatedCondition() $this->assertSame($element, $this->wait->until($condition)); } - public function testShouldDetectNotPresenceOfElementLocatedCondition() + public function testShouldDetectNotPresenceOfElementLocatedCondition(): void { $element = new RemoteWebElement(new RemoteExecuteMethod($this->driverMock), 'id'); @@ -140,7 +140,7 @@ public function testShouldDetectNotPresenceOfElementLocatedCondition() $this->assertTrue(call_user_func($condition->getApply(), $this->driverMock)); } - public function testShouldDetectPresenceOfAllElementsLocatedByCondition() + public function testShouldDetectPresenceOfAllElementsLocatedByCondition(): void { $element = $this->createMock(RemoteWebElement::class); @@ -157,7 +157,7 @@ public function testShouldDetectPresenceOfAllElementsLocatedByCondition() $this->assertSame([$element], $this->wait->until($condition)); } - public function testShouldDetectVisibilityOfElementLocatedCondition() + public function testShouldDetectVisibilityOfElementLocatedCondition(): void { // Set-up the consecutive calls to apply() as follows: // Call #1: throws NoSuchElementException @@ -181,7 +181,7 @@ public function testShouldDetectVisibilityOfElementLocatedCondition() $this->assertSame($element, $this->wait->until($condition)); } - public function testShouldDetectVisibilityOfAnyElementLocated() + public function testShouldDetectVisibilityOfAnyElementLocated(): void { $elementList = [ $this->createMock(RemoteWebElement::class), @@ -211,7 +211,7 @@ public function testShouldDetectVisibilityOfAnyElementLocated() $this->assertSame([$elementList[1], $elementList[2]], $this->wait->until($condition)); } - public function testShouldDetectInvisibilityOfElementLocatedConditionOnNoSuchElementException() + public function testShouldDetectInvisibilityOfElementLocatedConditionOnNoSuchElementException(): void { $element = $this->createMock(RemoteWebElement::class); @@ -232,7 +232,7 @@ public function testShouldDetectInvisibilityOfElementLocatedConditionOnNoSuchEle $this->assertTrue($this->wait->until($condition)); } - public function testShouldDetectInvisibilityOfElementLocatedConditionOnStaleElementReferenceException() + public function testShouldDetectInvisibilityOfElementLocatedConditionOnStaleElementReferenceException(): void { $element = $this->createMock(RemoteWebElement::class); @@ -253,7 +253,7 @@ public function testShouldDetectInvisibilityOfElementLocatedConditionOnStaleElem $this->assertTrue($this->wait->until($condition)); } - public function testShouldDetectInvisibilityOfElementLocatedConditionWhenElementBecamesInvisible() + public function testShouldDetectInvisibilityOfElementLocatedConditionWhenElementBecamesInvisible(): void { $element = $this->createMock(RemoteWebElement::class); @@ -274,7 +274,7 @@ public function testShouldDetectInvisibilityOfElementLocatedConditionWhenElement $this->assertTrue($this->wait->until($condition)); } - public function testShouldDetectVisibilityOfCondition() + public function testShouldDetectVisibilityOfCondition(): void { $element = $this->createMock(RemoteWebElement::class); $element->expects($this->exactly(2)) @@ -289,7 +289,7 @@ public function testShouldDetectVisibilityOfCondition() $this->assertSame($element, $this->wait->until($condition)); } - public function testShouldDetectElementTextContainsCondition() + public function testShouldDetectElementTextContainsCondition(): void { // Set-up the consecutive calls to apply() as follows: // Call #1: throws NoSuchElementException @@ -313,7 +313,7 @@ public function testShouldDetectElementTextContainsCondition() $this->assertTrue($this->wait->until($condition)); } - public function testShouldDetectElementTextIsCondition() + public function testShouldDetectElementTextIsCondition(): void { // Set-up the consecutive calls to apply() as follows: // Call #1: throws NoSuchElementException @@ -340,7 +340,7 @@ public function testShouldDetectElementTextIsCondition() $this->assertTrue($this->wait->until($condition)); } - public function testShouldDetectElementTextMatchesCondition() + public function testShouldDetectElementTextMatchesCondition(): void { // Set-up the consecutive calls to apply() as follows: // Call #1: throws NoSuchElementException @@ -368,7 +368,7 @@ public function testShouldDetectElementTextMatchesCondition() $this->assertTrue($this->wait->until($condition)); } - public function testShouldDetectElementValueContainsCondition() + public function testShouldDetectElementValueContainsCondition(): void { // Set-up the consecutive calls to apply() as follows: // Call #1: throws NoSuchElementException @@ -397,7 +397,7 @@ public function testShouldDetectElementValueContainsCondition() $this->assertTrue($this->wait->until($condition)); } - public function testShouldDetectNumberOfWindowsToBeCondition() + public function testShouldDetectNumberOfWindowsToBeCondition(): void { $this->driverMock->expects($this->any()) ->method('getWindowHandles') @@ -410,12 +410,10 @@ public function testShouldDetectNumberOfWindowsToBeCondition() $this->assertTrue(call_user_func($condition->getApply(), $this->driverMock)); } - /** - * @param RemoteWebElement $element - * @param int $expectedNumberOfFindElementCalls - */ - private function setupDriverToReturnElementAfterAnException($element, $expectedNumberOfFindElementCalls) - { + private function setupDriverToReturnElementAfterAnException( + RemoteWebElement $element, + int $expectedNumberOfFindElementCalls + ): void { $consecutiveReturn = [ $this->throwException(new NoSuchElementException('')), ]; diff --git a/tests/unit/WebDriverKeysTest.php b/tests/unit/WebDriverKeysTest.php index 757b7ff99..cfa07308f 100644 --- a/tests/unit/WebDriverKeysTest.php +++ b/tests/unit/WebDriverKeysTest.php @@ -1,4 +1,4 @@ -assertSame($expectedOssOutput, WebDriverKeys::encode($keys)); $this->assertSame($expectedW3cOutput, WebDriverKeys::encode($keys, true)); } @@ -24,7 +25,7 @@ public function testShouldEncodeKeysToFormatOfEachProtocol($keys, $expectedOssOu /** * @return array[] */ - public function provideKeys() + public function provideKeys(): array { return [ 'empty string' => ['', [''], ''], diff --git a/tests/unit/WebDriverOptionsTest.php b/tests/unit/WebDriverOptionsTest.php index 7b4b8f45e..4f51d12b6 100644 --- a/tests/unit/WebDriverOptionsTest.php +++ b/tests/unit/WebDriverOptionsTest.php @@ -1,7 +1,8 @@ -getMock(); } - public function testShouldAddCookieFromArray() + public function testShouldAddCookieFromArray(): void { $cookieInArray = [ 'name' => 'cookieName', @@ -42,7 +43,7 @@ public function testShouldAddCookieFromArray() $options->addCookie($cookieInArray); } - public function testShouldAddCookieFromCookieObject() + public function testShouldAddCookieFromCookieObject(): void { $cookieObject = new Cookie('cookieName', 'someValue'); $cookieObject->setPath('/bar'); @@ -70,18 +71,18 @@ public function testShouldAddCookieFromCookieObject() $options->addCookie($cookieObject); } - public function testShouldNotAllowToCreateCookieFromDifferentObjectThanCookie() + public function testShouldNotAllowToCreateCookieFromDifferentObjectThanCookie(): void { $notCookie = new \stdClass(); $options = new WebDriverOptions($this->executor); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Cookie must be set from instance of Cookie class or from array.'); $options->addCookie($notCookie); } - public function testShouldGetAllCookies() + public function testShouldGetAllCookies(): void { $this->executor->expects($this->once()) ->method('execute') @@ -117,7 +118,7 @@ public function testShouldGetAllCookies() $this->assertSame('secondCookie', $cookies[1]->getName()); } - public function testShouldGetCookieByName() + public function testShouldGetCookieByName(): void { $this->executor->expects($this->once()) ->method('execute') @@ -156,7 +157,7 @@ public function testShouldGetCookieByName() $this->assertTrue($cookie->isSecure()); } - public function testShouldReturnNullIfCookieWithNameNotFound() + public function testShouldReturnNullIfCookieWithNameNotFound(): void { $this->executor->expects($this->once()) ->method('execute') @@ -179,7 +180,7 @@ public function testShouldReturnNullIfCookieWithNameNotFound() $this->assertNull($options->getCookieNamed('notExistingCookie')); } - public function testShouldReturnTimeoutsInstance() + public function testShouldReturnTimeoutsInstance(): void { $options = new WebDriverOptions($this->executor); @@ -187,7 +188,7 @@ public function testShouldReturnTimeoutsInstance() $this->assertInstanceOf(WebDriverTimeouts::class, $timeouts); } - public function testShouldReturnWindowInstance() + public function testShouldReturnWindowInstance(): void { $options = new WebDriverOptions($this->executor); diff --git a/tools/php-cs-fixer/composer.json b/tools/php-cs-fixer/composer.json new file mode 100644 index 000000000..029b32c27 --- /dev/null +++ b/tools/php-cs-fixer/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "friendsofphp/php-cs-fixer": "^3.0" + } +} diff --git a/tools/phpstan/composer.json b/tools/phpstan/composer.json new file mode 100644 index 000000000..0ac8efe45 --- /dev/null +++ b/tools/phpstan/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "phpstan/phpstan": "^1.8" + } +}