3

JavaScript testing #18. E2E Playwright tests for uploading and downloading files

 3 weeks ago
source link: https://wanago.io/2024/04/22/javascript-testing-e2e-playwright-files/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

JavaScript testing #18. E2E Playwright tests for uploading and downloading files

Testing

April 22, 2024
This entry is part 18 of 18 in the JavaScript testing tutorial

Our applications sometimes include features that allow users to select files from their hard drives, and some functionalities might let users download files. Implementing End-to-End tests that ensure that everything works as expected might not seem straightforward at first.

In this article, we create a simple React application that lets users choose a JSON file from their machine and download a formatted version. Then, we use Playwright to test the file input and verify if the downloaded file is correct.

An application that formats JSON files

To start, let’s create a component with a file input.

JsonFormatter.tsx
import { useJsonFormatter } from './useJsonFormatter';
export const JsonFormatter = () => {
  const { handleJsonFile, error } = useJsonFormatter();
  return (
    <div
      style={{
        display: 'flex',
        flexDirection: 'column',
      <label htmlFor="json-file-input">Choose a valid JSON file</label>
      <input id="json-file-input" type="file" onChange={handleJsonFile} />
      {error && (
        <p data-testid="error-message" style={{ color: 'red' }}>
          {error}
        </p>
    </div>

Under the hood, we extract the text from the selected file and parse it as JSON. Then, we format it and send it back to the user.

useJsonFormatter.tsx
import { ChangeEvent, useState } from 'react';
export function useJsonFormatter() {
  const [error, setError] = useState<null | string>(null);
  const formatTextAsJson = (text: string) => {
    const data = JSON.parse(text);
    return JSON.stringify(data, null, 2);
  const downloadJson = (formattedJson: string, fileName: string) => {
    const anchor = document.createElement('a');
    const blob = new Blob([formattedJson], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    anchor.setAttribute('href', url);
    anchor.setAttribute('download', `${fileName}_formatted`);
    anchor.click();
  const handleJsonFile = async (event: ChangeEvent<HTMLInputElement>) => {
    const selectedFile = event.target.files?.[0];
    setError(null);
    if (!selectedFile) {
      return;
      const textFromFile = await selectedFile.text();
      const formattedJson = formatTextAsJson(textFromFile);
      downloadJson(formattedJson, selectedFile.name);
    } catch {
      setError('The provided file does not contain valid JSON');
    event.target.value = '';
  return {
    handleJsonFile,
    error,

If the user provides a valid JSON file, they get a formatted version with the same file name.

valid-1.png

If the user selects an invalid file, we display the error message.

invalid.png

Preparing the necessary files

To test our application, we must prepare two files – one with valid JSON and one with invalid data. Let’s put them in the directory called resources in the tests folder.

tests/resources/data.json
{"key": "value"}
tests/resources/invalidData.txt
{"key": "value

Interacting with the file input

To interact with the file input through our End-to-End tests, we must first locate it on the page. One of the straightforward ways of doing that is to find it through the label text.

page.getByLabel('Choose a valid JSON file')

Now, we must provide the path to our files with the setInputFiles function. One way is to provide a relative path to the file. The path will be resolved relative to the current working directory containing our package.json file.

await page
  .getByLabel('Choose a valid JSON file')
  .setInputFiles('./tests/resources/data.json');

We can use the above to test what happens when the user provides both valid and invalid files.

JsonFormatter.test.tsx
import { expect, test } from '@playwright/test';
test.describe('When the user visits the page', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  test.describe('and provides an invalid file', () => {
    test.beforeEach(async ({ page }) => {
      await page
        .getByLabel('Choose a valid JSON file')
        .setInputFiles('./tests/resources/invalidData.txt');
    test('it should display an error message', async ({ page }) => {
      await expect(page.getByTestId('error-message')).toBeVisible();
  test.describe('and provides a valid file', () => {
    test.beforeEach(async ({ page }) => {
      await page
        .getByLabel('Choose a valid JSON file')
        .setInputFiles('./tests/resources/data.json');
    test('it should not display an error message', async ({ page }) => {
      await expect(page.getByTestId('error-message')).not.toBeVisible();

To use page.goto('/') without providing the full URL we need to have the baseURL configuration set up. If you want to know more, check out JavaScript testing #17. Introduction to End-to-End testing with Playwright

Screenshot-from-2024-04-21-04-50-49.png

Testing the name of the downloaded file

Every time the browser downloads a file, Playwright emits an event. To start listening for it, we need to call the waitForEvent function, which returns a promise.

let downloadedFile: Download;
test.beforeEach(async ({ page }) => {
  const downloadedFilePromise = page.waitForEvent('download');
  await page
    .getByLabel('Choose a valid JSON file')
    .setInputFiles('./tests/resources/data.json');
  downloadedFile = await downloadedFilePromise;

The crucial aspect is to start waiting for the event before the browser downloads the file, but don’t use the await keyword until it is downloaded.

Now, we can use the suggestedFilename function to test if the downloaded file’s name is correct.

JsonFormatter.test.tsx
import { Download, expect, test } from '@playwright/test';
test.describe('When the user visits the page', () => {
  // ...
  test.describe('and provides a valid file', () => {
    let downloadedFile: Download;
    test.beforeEach(async ({ page }) => {
      const downloadedFilePromise = page.waitForEvent('download');
      await page
        .getByLabel('Choose a valid JSON file')
        .setInputFiles('./tests/resources/data.json');
      downloadedFile = await downloadedFilePromise;
    test('it should download a file with the correct name', async () => {
      const name = downloadedFile.suggestedFilename();
      expect(name).toEqual('data.json');
    // ...

Checking the contents of the file

We also want to check if our application correctly formatted the JSON file. We need to use the createReadStream function that returns a read stream to do that.

If you want to know more about streams in Node.js, check out Node.js TypeScript #4. Paused and flowing modes of a readable stream

Playwright tests run in a Node.js environment. Thanks to that, we can use various APIs built into Node.js. The most straightforward way of reading the stream is to use the text function from node:stream/consumers.

import { Download, expect, test } from '@playwright/test';
import { text } from 'node:stream/consumers';
test.describe('When the user visits the page', () => {
  // ...
  test.describe('and provides a valid file', () => {
    let downloadedFile: Download;
    test.beforeEach(async ({ page }) => {
      const downloadedFilePromise = page.waitForEvent('download');
      await page
        .getByLabel('Choose a valid JSON file')
        .setInputFiles('./tests/resources/data.json');
      downloadedFile = await downloadedFilePromise;
    test('it should download a file with the formatted JSON', async () => {
      const fileStream = await downloadedFile.createReadStream();
      const result = await text(fileStream);
      expect(result).toEqual('{\n  "key": "value"\n}');
    // ...
Screenshot-from-2024-04-21-05-31-28.png

Summary

In this article, we’ve learned how to use Playwright to write End-to-End tests to verify features that include dealing with files. This included learning how to simulate choosing a file from the hard drive. Besides that, we also learned how to verify downloaded files by checking their names and content to ensure they contain the correct data. Fortunately, Playwright makes working with files very straightforward.

Series Navigation<< JavaScript testing #17. Introduction to End-to-End testing with Playwright

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK