Repository: freeCodeCamp/freeCodeCamp
File: e2e/quiz-challenge.spec.ts (TypeScript)

Code before:
        await route.fulfill({
          contentType: 'application/json',
          body: JSON.stringify(pageData)
        });
      } catch (_err) {
        // If parsing fails, just continue the original response
        await route.continue();
      }
    });

    await page.goto(
      '/learn/full-stack-developer/quiz-basic-html/quiz-basic-html'
    );
  });

  test('should display a list of unanswered questions if user has not answered all questions', async ({
    page
  }) => {
    // Wait for the page content to render
    await expect(page.getByRole('radiogroup')).toHaveCount(20);
    const radioGroups = await page.getByRole('radiogroup').all();

    // The radio label contents are rendered with `PrismFormatted`.
    // For accessibility we must ensure:
    // - The formatted content is wrapped in a `span`
    // - No ARIA role is added to `pre` elements
    const firstGroup = radioGroups[0];
    const spanLabel = firstGroup.locator('span.quiz-answer-label');
    await expect(spanLabel).toHaveCount(4);

    const preWithRole = firstGroup.locator('pre[role]');
    await expect(preWithRole).toHaveCount(0);

    for (let i = 0; i < 15; i++) {
      const radioGroups = await page.getByRole('radiogroup').all();
      await radioGroups[i].getByRole('radio').first().click();
    }

    await page.getByRole('button', { name: 'Finish the quiz' }).click();

    await expect(
      page.getByText(
        'The following questions are unanswered: 16, 17, 18, 19, 20. You must answer all questions.'
      )
    ).toBeVisible();
  });

  test('should allow user to complete the block when they pass the quiz', async ({
    page
  }) => {
    test.setTimeout(20000);

Review comment (suggestion):
The challenge content may change, so text is not a stable selector. The most reliable way to select the correct answer is to find its `data-value`, as set in the code:

https://github.com/freeCodeCamp/freeCodeCamp/blob/c58ba56eebc2cf616fbca8577ceee35a0cbc1dfe/client/src/templates/Challenges/quiz/show.tsx#L159-L169

Though this query exposes the implementation detail, so it's much better to mock the entire challenge content and query by text instead.

Code after:
        challenge: { quizzes: Quiz[] };
      };
    };
  };
}

test.describe('Quiz challenge', () => {
  test.beforeEach(async ({ page }) => {
    const fixturePath = path.join(__dirname, 'fixtures', 'quiz-fixture.json');
    const fixture = JSON.parse(fs.readFileSync(fixturePath, 'utf8')) as Quiz[];

    // Intercept Gatsby page-data and inject a deterministic quiz fixture
    await page.route('**/page-data/**/page-data.json', async route => {
      const response = await route.fetch();
      const body = await response.text();

      const pageData = JSON.parse(body) as PageData;
      pageData.result.data.challengeNode.challenge.quizzes = fixture;

      await route.fulfill({
        contentType: 'application/json',
        body: JSON.stringify(pageData)
      });
    });

    await page.goto(
      '/learn/full-stack-developer/quiz-basic-html/quiz-basic-html'
    );
  });

  test('should display a list of unanswered questions if user has not answered all questions', async ({
    page
  }) => {
    // Wait for the page content to render
    await expect(page.getByRole('radiogroup')).toHaveCount(20);
    const radioGroups = await page.getByRole('radiogroup').all();

    // The radio label contents are rendered with `PrismFormatted`.
    // For accessibility we must ensure:
    // - The formatted content is wrapped in a `span`
    // - No ARIA role is added to `pre` elements
    const firstGroup = radioGroups[0];
    const spanLabel = firstGroup.locator('span.quiz-answer-label');
    await expect(spanLabel).toHaveCount(4);

    const preWithRole = firstGroup.locator('pre[role]');
    await expect(preWithRole).toHaveCount(0);

    for (let i = 0; i < 15; i++) {
      const radioGroups = await page.getByRole('radiogroup').all();
      await radioGroups[i].getByRole('radio').first().click();