diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e17374fe..d90ec246 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -83,7 +83,7 @@ jobs: verbose: true test_unit: - name: ${{ matrix.os }} - ${{ matrix.python-version }} + name: ${{ matrix.os }} - ${{ matrix.python-version }} - unit runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -145,17 +145,13 @@ jobs: restore-keys: | ${{ matrix.os }}-tox-${{ matrix.python-version }}- - - name: Run unit tests - if: ${{ ! matrix.tox-env && matrix.with-coverage }} - run: tox -e py${{ matrix.python-version }}-cov -- testing/test_unit.py + - name: Run unit tests with coverage + if: ${{ matrix.with-coverage }} + run: tox -e ${{ matrix.python-version }}-cov -- testing/test_unit.py - - name: Run unit tests - if: ${{ ! matrix.tox-env && ! matrix.with-coverage }} - run: tox -e py${{ matrix.python-version }} -- testing/test_unit.py - - - name: Run unit tests - if: ${{ matrix.tox-env }} - run: tox -e ${{ matrix.tox-env }} -- testing/test_unit.py + - name: Run unit tests without coverage + if: ${{ ! matrix.with-coverage }} + run: tox -e ${{ matrix.tox-env || matrix.python-version }} -- testing/test_unit.py - name: Upload coverage to codecov if: >- @@ -174,10 +170,7 @@ jobs: verbose: true test_integration: - name: ubuntu - ${{ matrix.python-version }} - needs: - - test_javascript - - test_unit + name: ubuntu - ${{ matrix.python-version }} - integration runs-on: ubuntu-latest strategy: fail-fast: false @@ -224,17 +217,13 @@ jobs: restore-keys: | ubuntu-latest-tox-${{ matrix.python-version }}- - - name: Run integration tests - if: ${{ ! matrix.tox-env && matrix.with-coverage }} + - name: Run integration tests with coverage + if: ${{ matrix.with-coverage }} run: tox -e ${{ matrix.python-version }}-cov -- testing/test_integration.py - - name: Run integration tests - if: ${{ ! matrix.tox-env && ! matrix.with-coverage }} - run: tox -e ${{ matrix.python-version }} -- testing/test_integration.py - - - name: Run integration tests - if: ${{ matrix.tox-env }} - run: tox -e ${{ matrix.tox-env }} -- testing/test_integration.py + - name: Run integration tests without coverage + if: ${{ ! matrix.with-coverage }} + run: tox -e ${{ matrix.tox-env || matrix.python-version }} -- testing/test_integration.py - name: Upload coverage to codecov if: >- @@ -251,3 +240,52 @@ jobs: flags: py_integration_tests name: ubuntu-latest-${{ matrix.python-version }} verbose: true + + test_e2e: + name: ubuntu - ${{ matrix.python-version }} - e2e + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10"] + include: + - python-version: pypy3.9 + tox-env: py3.9 + - python-version: 3.11-dev + tox-env: devel + + steps: + - name: Set newline behavior + run: git config --global core.autocrlf false + + - uses: actions/checkout@v3 + + - name: Start chrome + run: ./start + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: '16.x' + + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Ensure latest pip + run: python -m pip install --upgrade pip + + - name: Install tox + run: python -m pip install --upgrade tox + + - name: Cache tox virtual environment + uses: actions/cache@v3 + with: + path: .tox + key: ubuntu-latest-tox-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml', 'tox.ini') }} + restore-keys: | + ubuntu-latest-tox-${{ matrix.python-version }}- + + - name: Run e2e tests + run: tox -e ${{ matrix.tox-env || matrix.python-version }} -- testing/test_e2e.py diff --git a/src/pytest_html/scripts/storage.js b/src/pytest_html/scripts/storage.js index 4424db4c..b8254314 100644 --- a/src/pytest_html/scripts/storage.js +++ b/src/pytest_html/scripts/storage.js @@ -11,10 +11,19 @@ const possibleFilters = possibleResults.map((item) => item.result) const getVisible = () => { const url = new URL(window.location.href) - const settings = new URLSearchParams(url.search).get('visible') || '' - return settings ? - [...new Set(settings.split(',').filter((filter) => possibleFilters.includes(filter)))] : possibleFilters + const settings = new URLSearchParams(url.search).get('visible') + const lower = (item) => { + const lowerItem = item.toLowerCase() + if (possibleFilters.includes(lowerItem)) { + return lowerItem + } + return null + } + return settings === null ? + possibleFilters : + [...new Set(settings?.split(',').map(lower).filter((item) => item))] } + const hideCategory = (categoryToHide) => { const url = new URL(window.location.href) const visibleParams = new URLSearchParams(url.search).get('visible') diff --git a/testing/test_e2e.py b/testing/test_e2e.py new file mode 100644 index 00000000..de09990f --- /dev/null +++ b/testing/test_e2e.py @@ -0,0 +1,78 @@ +# write a test that sorts the table and asserts the order. +# sort default columns and custom sortable column +import os +import urllib.parse + +import pytest +import selenium.webdriver.support.expected_conditions as ec +from assertpy import assert_that +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.wait import WebDriverWait + +pytest_plugins = ("pytester",) + + +@pytest.fixture +def driver(pytester): + chrome_options = webdriver.ChromeOptions() + if os.environ.get("CI", False): + chrome_options.add_argument("--headless") + chrome_options.add_argument("--window-size=1920x1080") + driver = webdriver.Remote( + command_executor="http://127.0.0.1:4444", options=chrome_options + ) + + yield driver + driver.quit() + + +@pytest.fixture +def path(pytester): + def func(path="report.html", cmd_flags=None): + cmd_flags = cmd_flags or [] + + path = pytester.path.joinpath(path) + pytester.runpytest("--html", path, *cmd_flags) + + # Begin workaround + # See: https://github.com/pytest-dev/pytest/issues/10738 + path.chmod(0o755) + for parent in path.parents: + try: + os.chmod(parent, 0o755) + except PermissionError: + continue + # End workaround + + return path + + return func + + +def _encode_query_params(params): + return urllib.parse.urlencode(params) + + +def test_visible(pytester, path, driver): + pytester.makepyfile( + """ + def test_pass_one(): pass + def test_pass_two(): pass + """ + ) + + driver.get(f"file:///reports{path()}") + WebDriverWait(driver, 5).until( + ec.visibility_of_all_elements_located((By.CSS_SELECTOR, "#results-table")) + ) + result = driver.find_elements(By.CSS_SELECTOR, "tr.collapsible") + assert_that(result).is_length(2) + + query_params = _encode_query_params({"visible": ""}) + driver.get(f"file:///reports{path()}?{query_params}") + WebDriverWait(driver, 5).until( + ec.visibility_of_all_elements_located((By.CSS_SELECTOR, "#results-table")) + ) + result = driver.find_elements(By.CSS_SELECTOR, "tr.collapsible") + assert_that(result).is_length(0) diff --git a/testing/unittest.js b/testing/unittest.js index 67f6654b..540777ca 100644 --- a/testing/unittest.js +++ b/testing/unittest.js @@ -80,6 +80,43 @@ describe('Filter tests', () => { ]) }) }) + describe('getVisible', () => { + let originalWindow + + after(() => global.window = originalWindow) + + it('returns all filters by default', () => { + mockWindow() + const visibleItems = storageModule.getVisible() + expect(visibleItems).to.eql(storageModule.possibleFilters) + }) + + it('returns specified filters', () => { + mockWindow('visible=failed,error') + const visibleItems = storageModule.getVisible() + expect(visibleItems).to.eql(['failed', 'error']) + }) + + it('handles case insensitive params', () => { + mockWindow('visible=fAiLeD,ERROR,passed') + const visibleItems = storageModule.getVisible() + expect(visibleItems).to.eql(['failed', 'error', 'passed']) + }) + + const falsy = [ + { param: 'visible' }, + { param: 'visible=' }, + { param: 'visible=""' }, + { param: 'visible=\'\'' }, + ] + falsy.forEach(({ param }) => { + it(`returns no filters with ${param}`, () => { + mockWindow(param) + const visibleItems = storageModule.getVisible() + expect(visibleItems).to.be.empty + }) + }) + }) }) @@ -153,18 +190,20 @@ describe('Sort tests', () => { }) }) +const mockWindow = (queryParam) => { + const mock = { + location: { + href: `https://example.com/page?${queryParam}`, + }, + } + originalWindow = global.window + global.window = mock +} + describe('Storage tests', () => { describe('getCollapsedCategory', () => { let originalWindow - const mockWindow = (queryParam) => { - const mock = { - location: { - href: `https://example.com/page?${queryParam}`, - }, - } - originalWindow = global.window - global.window = mock - } + after(() => global.window = originalWindow) it('collapses passed by default', () => {