Webブラウザの操作を自動化で行いたい処理の一つに、ファイルのアップロードがある。いちいちファイル選択ダイアログで選ぶのは面倒だからだ。そしてこの処理は自動化に失敗しやすい部分でもある。今回は、アップロードの自動化について取り上げる。

→連載「PowerShell Core入門 - 基本コマンドの使い方」の過去回はこちらを参照。

ファイル選択に使うサンプルページ

今回も前回と同じ「https://getbootstrap.jp/docs/5.0/examples/cheatsheet/」をサンプルページとして使用する。

  • https://getbootstrap.jp/docs/5.0/examples/cheatsheet/

    https://getbootstrap.jp/docs/5.0/examples/cheatsheet/

このページの上から3分の1くらいのところにファイル選択のボタン(input要素)が用意されているので、これを使う。例のごとく、要素を操作するにはXPathが必要になるので、Microsoft Edgeの開発者モードで対象となる要素のXPathを調べておく。

  • Microsoft Edgeの開発者モードでXPathを調べる

    Microsoft Edgeの開発者モードでXPathを調べる

要素 XPath
ファイル選択 //∗[@id="customFile"]
その下のスイッチ //∗[@id="flexSwitchCheckChecked"]

なお、操作上使うことになるので、ファイル選択ボタンの下にあるスイッチボタンのXPathも調べて上の表に加えてある。

PowerShellから操作していく

では早速、PowerShellから操作していく。まず、WebDriverおよびMicrosoft Edgeを起動し、さらにhttps://getbootstrap.jp/docs/5.0/examples/cheatsheet/をオープンする。「webdriver_edge_start.ps1」は稿末に付録として掲載してあるので参考にしていただきたい。

webdriver_edge_start.ps1
Set-SeUrl -Url https://getbootstrap.jp/docs/5.0/examples/cheatsheet/

これでhttps://getbootstrap.jp/docs/5.0/examples/cheatsheet/が開くので、とりあえず手動で3分の1ほどスクロールし、ターゲットとなるファイル選択ボタンを表示させておく。

  • https://getbootstrap.jp/docs/5.0/examples/cheatsheet/ の操作対象となる部分

    https://getbootstrap.jp/docs/5.0/examples/cheatsheet/ の操作対象となる部分

XPathを指定して対象となるファイル選択用のinput要素を取得する。

$Element = Get-SeElement -By XPath -Value '//*[@id="customFile"]'

ファイルの選択は、Invoke-SeKeysコマンドレットでファイルパスを送ることで実行できる。ここでは「~\Desktop\images.zip」というファイルを選択することにして、次のようにファイルパスを送る。

Invoke-SeKeys -Element $Element -Keys $env:HOME+"\Desktop\images.zip"

本来、これでファイルパスが設定されるのだが、Microsoft Edgeでは次のようにファイル選択用のダイアログが起動してくる。パスは全く関係ない状態になっている。

  • ファイル選択のダイアログが起動してくる

    ファイル選択のダイアログが起動してくる

Google ChromeなどほかのWebブラウザだと先ほどの処理でファイルパスが設定されるのだが、Microsoft Edgeだとそうならない。実装を追っていないので確かなことはわからないが、セキュリティポリシーでこの処理を禁止しているように思われる。自動的にファイルをアップロードされるというのはセキュリティ上は好ましくないということから、禁止しているのかもしれない。

しかし、これでは困る。自動化したい。

ファイル選択のダイアログはOSの管轄になるので、Selenium (WebDriver)経由では操作できない。このままでは、せっかくほかの処理を自動化してもファイルの選択だけは手動で行うことになる。それでは中途半端というものだ。

OSの管轄なのであれば、OSの提供する機能を使ってファイル選択ダイアログの操作を自動化すればよい。幸い、PowerShellは.NETの機能にアクセスできるので、OSが管轄する機能に関しても結構いろいろなことができる。この辺り、PowerShellはWindowsとの相性が良い。PowerShellが提供していない機能でも、いざとなったら.NETやWindows APIの機能を引っ張ってきて使ってしまえばよいので、やろうと思えばかなりのことができるのだ。

今回はSystem.Windows.Forms経由でウインドウにキー入力を送り込めば解決する。そこで、この機能を使うためにadd-typeを実行する。

add-type -AssemblyName System.Windows.Forms

そして、先ほどと同じようにしてファイル選択ダイアログを起動し、確実にファイル選択ダイアログが起動してフォーカスがそこに移動するまで処理待ちを行う。

Invoke-SeKeys -Element $Element -Keys $env:HOME
sleep 0.5

ここまで準備したら.NETの機能を使ってファイルパスの入力と選択処理を行う。

[System.Windows.Forms.SendKeys]::SendWait("C:\Users\daichi\Desktop\images.zip`n")

これでアクティブになっているウインドウに「C:\Users\daichi\Desktop\images.zip改行」というデータが送信される。実際には、ファイル選択ダイアログの「ファイル名」のところにファイルパスが入力されていく様子を見ることができる。

  • ファイル選択ダイアログが起動してきて自動的にパスが入力され、そのまま「開く」がクリックされる

    ファイル選択ダイアログが起動してきて自動的にパスが入力され、そのまま「開く」がクリックされる

最後に改行が入っているので、改行が押された状態になる。つまり「開く」ボタンが押されたことになって、ファイル選択ダイアログは終了する。

そしてMicrosoft Edgeに表示されていたファイル選択要素のところに、選択したファイルが表示されることを確認できる。

  • ファイル選択が行われた状態になる

    ファイル選択が行われた状態になる

Seleniumの機能だけではなく.NETの機能も使っているが、実はファイル選択の処理はSeleniumだけだと処理できないケースがあり、その場合にはここで取り上げたようにOSの機能を使って自動操作を続けることになる。このやり方は覚えておいて損はない。

エラーを回避する - ページのスクロール処理

実は、今回取り上げた処理をページを開いた後で実行すると、次のようにエラーになる。

PS C:\Users\daichi> Invoke-SeKeys -Element $Element -Keys $env:HOME
MethodInvocationException: Exception calling "Perform" with "0" argument(s): "move target out of bounds: viewport size: 1139,
670   (Session info: MicrosoftEdge=108.0.1462.46)"
PS C:\Users\daichi>

ファイル選択ボタンが表示エリア外にあるので、処理がエラーになっている。要するに、処理を行う前に見えるところまでスクロールする必要があるのだ。

スクロールする方法はいくつかあるのだが、ここではごく簡単な方法で行うことにする。ファイル選択ボタンの下にスイッチボタンがあるので、これをクリックする処理を先に行う。クリックするときにスクロール処理が行われるので、それを利用するわけだ。クリックを2回行えばスイッチは元に戻るので、影響もない。

処理としては次のような感じになる。

$Element = Get-SeElement -By XPath -Value '//*[@id="flexSwitchCheckChecked"]'
Invoke-SeClick -Element $Element -Action Click
Invoke-SeClick -Element $Element -Action Click

ページを開いた直後は次のようになっている。

  • 実行前

    実行前

先ほどの処理を実行すると次のようになる。

  • 実行後 (スクロールされていることを確認)

    実行後 (スクロールされていることを確認)

必要な位置までスクロールが行われたことを確認できる。もっと厳密に行う方法もあるのだが、とりあえず今回はこれで良いだろう。

今回の処理のまとめ

今回行ったファイル選択の処理をスクリプトにまとめると、次のようになる。

# WebDriver起動
webdriver_edge_start.ps1

# サンプルページをオープン
Set-SeUrl -Url https://getbootstrap.jp/docs/5.0/examples/cheatsheet/

# ダミー処理でページをスクロール
$Element = Get-SeElement -By XPath -Value '//*[@id="flexSwitchCheckChecked"]'
Invoke-SeClick -Element $Element -Action Click      
Invoke-SeClick -Element $Element -Action Click      

# ファイル選択ダイアログをオープン
$Element = Get-SeElement -By XPath -Value '//*[@id="customFile"]'
Invoke-SeKeys -Element $Element -Keys $env:HOME
sleep 0.5

# .NET経由でファイル選択ダイアログにおけるファイル選択操作を行う
add-type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait($env:HOME+"\Desktop\images.zip`n")

#webdriver_edge_stop.ps1

ファイルの選択が行えるようになると、実際のWebページやWebアプリケーションでの利用の幅が広がる。これまでに取り上げてきた機能だけでも結構な自動化ができるようになっているはずだ。ぜひ自分の業務に応用できるものがないか探して、少しずつでも適用していっていただきたい。

付録

webdriver_edge_start.ps1

#!/usr/bin/env pwsh

#========================================================================
# Microsoft Edge WebDriverを起動する
#========================================================================

#========================================================================
# 動作しているMicrosoft Edge WebDriverをすべて終了
#========================================================================
webdriver_edge_stop.ps1

#========================================================================
# Seleniumモジュールがない場合にはインストール
#========================================================================
if (-Not (Get-InstalledModule -Name Selenium 2> $Null)) {
    'Seleniumモジュールをインストールします。'
    Install-Module -Name Selenium -AllowPrerelease -Force
    Get-InstalledModule -Name Selenium
}

#========================================================================
# Microsoft Edge WebDriverを起動
#========================================================================
'Microsoft Edge WebDriverを起動します。'
$Size = '1200,800'
if  (-Not (Start-SeDriver -Browser Edge -Size $Size 2> $Null 3> $Null))
{
    #================================================================
    # Microsoft EdgeとMicrosoft Edge WebDriverのバージョンが一致して
    # いないためにドライバが動作しなかった可能性がある。
    #================================================================

    #================================================================
    # 不要なドライバプロセスを終了
    #================================================================
    webdriver_edge_stop.ps1

    #================================================================
    # Microsoft Edgeのバージョン番号
    #================================================================
    $EdgeDir='C:\Program Files (x86)\Microsoft\Edge\Application\'
    $EdgeVersion=(  Get-ChildItem -Name $EdgeDir                    | 
                    Where-Object { $_ -NotMatch "[a-zA-Z]+" }       |
                    Select-Object -First 1                          )
                    # ↑ 【Select-Object -First 1の理由】
                    # 更新前のバージョンと更新後のバージョンが同時に
                    # 存在するタイミングがあるので、更新後のバージョン
                    # のみを取得するためにSelect-Objectを実行している。

    #================================================================
    # Microsoft Edge WebDriverダウンロードURLとデプロイ先パス
    #================================================================
    $DriverURL="https://msedgedriver.azureedge.net/$EdgeVersion/edgedriver_win64.zip"

    $SeModVer=(Get-InstalledModule -Name Selenium).Version -replace "-.+$",""
    $DriverDir="$env:HOME\Documents\powershell\Modules\Selenium\$SeModVer\assemblies"
    $DriverDownloadDir="$DriverDir\_download"

    #================================================================
    # WebDriverダウンロード用の一時ディレクトリを作成
    #================================================================
    New-Item        $DriverDownloadDir -ItemType Directory -Force

    #================================================================
    # Microsoft Edgeと同じバージョンのMicrosoft Edge WebDriverを
    # ダウンロード
    #================================================================
    "Microsoft Edge WebDriver version $EdgeVersion をダウンロードします。"
    curl            -get                                            `
                    -o      $DriverDownloadDir\edgedriver_win64.zip `
                    $DriverURL

    #================================================================
    # Microsoft Edge WebDriverをデプロイ
    #================================================================
    "Microsoft Edge WebDriver version $EdgeVersion をインストールします。"
    Expand-Archive  -Path $DriverDownloadDir\edgedriver_win64.zip   `
                    -Destination $DriverDownloadDir                 `
                    -Force

    Copy-Item       -Path $DriverDownloadDir\msedgedriver.exe       `
                    -Destination $DriverDir\msedgedriver.exe        `
                    -Force

    #================================================================
    # WebDriverダウンロード用の一時ディレクトリを削除
    #================================================================
    Remove-Item     $DriverDownloadDir -Recurse -Force

    #================================================================
    # Microsoft Edge WebDriverを起動する
    #================================================================
    if      (-Not (Start-SeDriver -Browser Edge -Size $Size 2> $Null 3> $Null)) 
    {
            #========================================================
            # 原因不明の起動不能
            #========================================================

            #========================================================
            # 不要なドライバプロセスを終了
            #========================================================
            webdriver_edge_stop.ps1

            Exit
    }
}
'Microsoft Edge WebDriverの起動処理完了。'

webdriver_edge_stop.ps1

#!/usr/bin/env pwsh

#========================================================================
# Microsoft Edge WebDriverを終了する
#========================================================================

#========================================================================
# WebDriverプロセスを終了
#========================================================================
if  (Get-Process -Name msedgedriver 2> $Null) 
{
    '動作しているMicrosoft Edge WebDriverを終了します。'
    Get-Process -Name msedgedriver 2> $Null

    # Microsoft Edge WebDriverを終了
    Stop-SeDriver 2> $Null

    # まだ動作しているほかのMicrosoft Edge WebDriverを終了
    if      (Get-Process -Name msedgedriver 2> $Null) 
    {
            Get-Process -Name msedgedriver 2> $Null | Stop-Process
    }

    '動作しているMicrosoft Edge WebDriverの終了処理完了。'
}