find-cypress-specs
Version: 
Find Cypress spec files using the config settings
308 lines (266 loc) • 7.51 kB
JavaScript
// @ts-check
import {
  pickTaggedTestsFrom,
  pickTaggedTests,
  removeEmptyNodes,
  doTagsMatch,
  combineTags,
  preprocessAndTags,
} from './tagged'
const { addCounts, countTests, countPendingTests } = require('./count')
function countTheseTests(testsJson) {
  const specsN = Object.keys(testsJson).length
  let testsN = 0
  Object.keys(testsJson).forEach((filename) => {
    const n = testsJson[filename].counts.tests
    testsN += n
  })
  return { specsN, testsN }
}
// poor man's bundle
const htmlScripts = `
  ${countTheseTests.toString()}
  ${countPendingTests.toString()}
  ${addCounts.toString()}
  ${countTests.toString()}
  ${preprocessAndTags.toString()}
  ${doTagsMatch.toString()}
  ${combineTags.toString()}
  ${removeEmptyNodes.toString()}
  ${pickTaggedTests.toString()}
  ${pickTaggedTestsFrom.toString()}
  ${testsToHtml.toString()}
`
const styles = `
:root {
    --primary-color: 
    --secondary-color: 
    --background-color: 
    --text-color: 
    --border-color: 
    --tag-bg: 
    --tag-color: 
    --pending-color: 
  }
  body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
      Oxygen, Ubuntu, Cantarell, sans-serif;
    line-height: 1.6;
    color: var(--text-color);
    background-color: var(--background-color);
    margin: 0;
    padding: 2rem;
  }
  header {
    background-color: white;
    padding: 1.5rem;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    margin-bottom: 2rem;
  }
  h1 {
    margin: 0 0 1rem 0;
    color: var(--primary-color);
  }
  .filter-tags {
    margin: 1rem 0;
    display: flex;
    gap: 1rem;
    flex-wrap: wrap;
    align-items: center;
  }
  .filter-tag-container {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    white-space: nowrap;
  }
  .filter-tag {
    margin-right: 0.1rem;
  }
  .filter-tag-name {
    color: var(--secondary-color);
    font-weight: 500;
  }
  .filter-tag-count {
    color: var(--pending-color);
    font-weight: 300;
  }
  .specs {
    list-style-type: none;
    padding: 0;
    margin: 0;
  }
  .spec {
    background-color: white;
    border-radius: 8px;
    padding: 1.5rem;
    margin-bottom: 1.5rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
  .filename {
    color: var(--primary-color);
    margin: 0 0 1rem 0;
    font-size: 1.2rem;
    border-bottom: 2px solid var(--border-color);
    padding-bottom: 0.5rem;
  }
  .tests {
    list-style-type: none;
    padding-left: 1.5rem;
    margin: 0;
  }
  .suite {
    margin: 0.5rem 0;
    color: var(--primary-color);
    font-weight: 500;
  }
  .test {
    margin: 0.25rem 0;
    color: var(--text-color);
  }
  .tag {
    background-color: var(--tag-bg);
    color: var(--tag-color);
    padding: 0.2em 0.5em;
    border-radius: 4px;
    font-size: 0.9em;
    margin-left: 0.5rem;
    font-weight: normal;
  }
  .pending {
    color: var(--pending-color);
    font-style: italic;
  }
  .name {
    font-weight: 500;
  }
  .test .name {
    font-weight: 300;
  }
`
function testsToHtml(tests) {
  if (!Array.isArray(tests)) {
    return ''
  }
  return `
    <ul class="tests">
      ${tests
        .map((test) => {
          const tags = test.tags || []
          const tagList = tags
            .map((tag) => `<span class="tag">${tag}</span>`)
            .join('\n')
          if (test.type === 'test') {
            const classNames = test.pending ? 'test pending' : 'test'
            return `<li class="${classNames}"><span class="name">${test.name}</span> ${tagList}</li>`
          } else if (test.type === 'suite') {
            const suitesHtml = testsToHtml(test.suites)
            const testsHtml = testsToHtml(test.tests)
            return `
              <li class="suite">
                <span class="name">${test.name}</span> ${tagList}
                ${suitesHtml}
                ${testsHtml}
              </li>
            `
          } else {
            throw new Error(`Unknown test type: ${test.type}`)
          }
        })
        .join('\n')}
    </ul>
  `
}
/**
 * Takes the test JSON object with specs and tags
 * and returns a full static self-contained HTML file
 * that can be used to display the tests in a browser.
 * @param {Object} testsJson - The test JSON object with specs and tags
 * @param {Object} tagTestCounts - The tag test counts
 * @returns {string} - The HTML string
 */
function toHtml(testsJson, tagTestCounts = {}) {
  const { specsN, testsN } = countTheseTests(testsJson)
  // show tags in alphabetical order
  const allTags = Object.keys(tagTestCounts).sort()
  const html = `
    <html>
      <head>
        <title>Cypress Tests</title>
        <style>
          ${styles}
        </style>
        <script>
          ${htmlScripts}
          window.findCypressSpecs = ${JSON.stringify({
            tests: testsJson,
            tags: tagTestCounts,
            allTags,
            selectedTags: [],
          })}
          window.findCypressSpecs.render = () => {
            // get the selected tags from the checkboxes
            const selectedTags = Array.from(document.querySelectorAll('input.filter-tag:checked'))
              .map((checkbox) => checkbox.value)
            // filter the tests based on the selected tags
            let filtered = window.findCypressSpecs.tests
            if (selectedTags.length) {
              const testCopy = structuredClone(window.findCypressSpecs.tests)
              filtered = pickTaggedTestsFrom(testCopy, selectedTags)
            }
            const { specsN, testsN } = countTheseTests(filtered)
            document.querySelector('#specs-count').textContent = specsN
            document.querySelector('#tests-count').textContent = testsN
            // form the entire HTML first
            // then attach it to the DOM for speed
            let specsHtml = ''
            Object.keys(filtered).forEach(function (filename) {
              const tests = filtered[filename].tests
              specsHtml += '<li class="spec"><h2 class="filename">' + filename + '</h2>' + testsToHtml(tests) + '</li>'
            })
            const specsElement = document.querySelector('.specs')
            specsElement.innerHTML = specsHtml
          }
        </script>
      </head>
      <body>
        <header>
          <h1>Cypress Tests</h1>
          <p>
            <span id="specs-count">${specsN}</span> specs, <span id="tests-count">${testsN}</span> tests
          </p>
          <p class="filter-tags">
            ${allTags
              .map(
                (tag) =>
                  `<span class="filter-tag-container"><input type="checkbox" class="filter-tag" value="${tag}" onchange="window.findCypressSpecs.render()"/>
                     <span class="filter-tag-name">${tag}</span>
                     <span class="filter-tag-count">(${tagTestCounts[tag]})</span>
                  </span>`,
              )
              .join(' ')}
          </p>
        </header>
        <main>
          <ul class="specs">
            ${Object.keys(testsJson)
              .map((filename) => {
                return `
                <li class="spec">
                  <h2 class="filename">${filename}</h2>
                  ${testsToHtml(testsJson[filename].tests)}
                </li>
              `
              })
              .join('\n')}
          </ul>
        </main>
      </body>
    </html>
  `
  return html
}
module.exports = {
  toHtml,
}