【Golang】playwright-goでファイルをアップロードする

概要

PlaywrightはE2Eテストを行うためのツールですが、ブラウザを自動操作することができるので、
作業の自動化にも使用できます。
JavascirptやPythonなどでもPlaywrightを使用できますが、playwright-goがあるので、GolangでもPlaywrightを使用できます。

ファイルをアップロードする自動化を行うときに、input要素に対してはSetInputFilesが使用できるのですが、
input以外の要素だとSetInputFilesは動作しないため、別の方法でファイルをアップロードする必要があります。

テストアップロード環境の準備

ファイルをアップロードする環境をhtml、javascript、golangを使用して用意します。
下記のindex.html、main.js、main.goを同じパスに配置し、go run main.goでサーバーを起動します。

index.html

 1<!DOCTYPE html>
 2<html lang="ja">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6    <title>ファイルアップロード</title>
 7    <style>
 8        #drop-area {
 9            width: 300px;
10            height: 200px;
11            border: 2px dashed #ccc;
12            text-align: center;
13            padding: 20px;
14            margin: 50px auto;
15        }
16    </style>
17</head>
18<body>
19
20<div id="drop-area" ondragover="allowDrop(event)" ondrop="handleDrop(event)">
21    ファイルをここにドロップ
22</div>
23
24<script src="main.js"></script>
25
26</body>
27</html>

main.js

 1function allowDrop(event) {
 2  event.preventDefault();
 3}
 4
 5function handleDrop(event) {
 6  event.preventDefault();
 7
 8  var files = event.dataTransfer.files;
 9
10  if (files.length > 0) {
11    var file = files[0];
12    uploadFile(file);
13  }
14}
15
16function uploadFile(file) {
17  var formData = new FormData();
18  formData.append('file', file);
19
20  fetch('/upload', {
21    method: 'POST',
22    body: formData,
23  })
24    .then((response) => response.json())
25    .then((data) => {
26      console.log('File uploaded successfully:', data);
27    })
28    .catch((error) => {
29      console.error('Error uploading file:', error);
30    });
31}

main.go

 1package main
 2
 3import (
 4	"fmt"
 5	"io"
 6	"net/http"
 7	"os"
 8)
 9
10func main() {
11	http.HandleFunc("/upload", uploadHandler)
12	http.Handle("/", http.FileServer(http.Dir(".")))
13
14	port := 8080
15	fmt.Printf("Server is running on http://localhost:%d\n", port)
16	err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
17	if err != nil {
18		fmt.Println("Error starting server:", err)
19	}
20}
21
22func uploadHandler(w http.ResponseWriter, r *http.Request) {
23	r.ParseMultipartForm(1000000) // 1MB制限
24
25	file, handler, err := r.FormFile("file")
26	if err != nil {
27		fmt.Println("Error retrieving the file:", err)
28		w.WriteHeader(http.StatusInternalServerError)
29		return
30	}
31	defer file.Close()
32
33	fmt.Printf("Received file: %+v\n", handler.Filename)
34
35	f, err := os.Create("./" + handler.Filename)
36	if err != nil {
37		fmt.Println("Error creating file:", err)
38		w.WriteHeader(http.StatusInternalServerError)
39		return
40	}
41	defer f.Close()
42
43	io.Copy(f, file)
44
45	w.Header().Set("Content-Type", "application/json")
46	w.WriteHeader(http.StatusOK)
47	w.Write([]byte(`{"message": "File uploaded successfully"}`))
48}

サーバーを起動してhttp://localhost:8080/にアクセスすると、ブラウザに「ファイルをここにドロップ」が表示されます。

「ファイルをここにドロップ」と表示されているエリアに1MB以内のファイルをドラッグ&ドロップすると、サーバーにファイルがアップロードされます。

playwright-goでファイルをアップロード

先ほどのindex.htmlだと、div要素のid="drop-area"にファイルをドラッグ&ドロップするとファイルをアップロードできますが、input要素ではないのでplaywright-goのSetInputFilesだと動作しません。

1<div id="drop-area" ondragover="allowDrop(event)" ondrop="handleDrop(event)">
2    ファイルをここにドロップ
3</div>

下記のコードではChromeを使用して、SetInputFiles以外の方法でtest.txtファイルをアップロードします。

 1package main
 2
 3import (
 4	"encoding/base64"
 5	"fmt"
 6	"os"
 7
 8	"github.com/playwright-community/playwright-go"
 9)
10
11func main() {
12	runOption := &playwright.RunOptions{
13		SkipInstallBrowsers: true,
14	}
15
16	if err := playwright.Install(runOption); err != nil {
17		panic(err)
18	}
19
20	pw, err := playwright.Run()
21	if err != nil {
22		panic(err)
23	}
24	defer pw.Stop()
25
26	contextOption := playwright.BrowserTypeLaunchPersistentContextOptions{
27		Channel:    playwright.String("chrome"),
28		Headless:   playwright.Bool(false),
29		Timeout:    playwright.Float(0),
30		NoViewport: playwright.Bool(true),
31	}
32
33	// ユーザーディレクトリは絶対パスで指定する必要があります
34	userDataDir := `C:\userDataDir`
35	browser, err := pw.Chromium.LaunchPersistentContext(userDataDir, contextOption)
36	if err != nil {
37		panic(err)
38	}
39	defer browser.Close()
40
41	page := browser.Pages()[0]
42	defer page.Close()
43
44	page.Goto("http://localhost:8080")
45
46	bytes, err := os.ReadFile("test.txt")
47	if err != nil {
48		panic(err)
49	}
50	fileBase64 := base64.StdEncoding.EncodeToString(bytes)
51	js := `
52 (data) => {
53            const dt = new DataTransfer();
54            // Convert the binary string to a hexadecimal string
55            const hexString = Uint8Array.from(atob(data), c => c.charCodeAt(0));
56            const file = new File([hexString], 'test.txt', { type: 'text/plain' });
57            dt.items.add(file);
58            return dt;
59        }
60	`
61	handle, err := page.EvaluateHandle(js, fileBase64)
62	if err != nil {
63		panic(err)
64	}
65
66	element := page.Locator("#drop-area")
67	if err := element.DispatchEvent("drop", map[string]interface{}{"dataTransfer": handle}); err != nil {
68		panic(err)
69	}
70
71	fmt.Println("ファイルをアップロードしました。")
72}

コードを実行するとサーバー側のターミナルで「Received file: test.txt」と表示されて、サーバーを起動しているパスにtest.txtがアップロードされます。

1Server is running on http://localhost:8080
2Received file: test.txt

参考サイト

関連ページ