UWSCRの新機能：ブラウザ操作

UWSCではCOMインターフェースを介してのInternet Explorer操作が主流でしたが、UWSCRでは標準機能としてGoogle ChromeおよびMicrosoft Edgeの自動操作に対応しています。

:::note info
[UwscWebDriver](https://github.com/stuncloud/UwscWebDriver)なんてのもありましたが、これはすでに開発を終了しています。
:::

UWSCRのブラウザ操作は

1. ブラウザを起動または再接続し`Browserオブジェクト`を得る
1. 操作したいタブの`TabWindowオブジェクト`を得る
1. タブ上の`document`オブジェクトからDOMへアクセスする

という流れでブラウザの操作を行います。擬似的なDOMのオブジェクトを得られるため、使い勝手はUWSCのIE操作に近くなっています。

:::note info
UWSCRのブラウザ操作には`Chrome Devtools Protocol`が使われています。MSEdgeもChromiumベースとなったことでこれをサポートしています。
:::


# ブラウザの起動およびBrowserオブジェクトの取得

以下の組み込み関数を実行するとブラウザの起動、または以前起動したブラウザへの再接続を試み成功した場合にBrowserオブジェクトを返します。

## BrowserControl()

ブラウザ種別を示す定数とブラウザのデバッギングポート番号を指定することでブラウザを起動または再接続します。再接続はすでに同一ポートが開かれている同一ブラウザが存在する場合に行われます。

```php
// msedgeのBrowserオブジェクトを得る
msedge = BrowserControl(BC_MSEDGE) // デフォルトでは9222ポートを使用

// google chromeのBrowserオブジェクトを得る
// 同じポートは使えないので別の値を指定
chrome = BrowserControl(BC_CHROME, 9223)
```

:::note warn
ブラウザのデバッギングポートが開放されている場合、そのポートを介してブラウザのあらゆる情報にアクセスできてしまいます。デバッギングポートが開かれたブラウザの取り扱いには注意してください。
:::

## BrowserBuilder()

`BrowserBuilder`関数は`BrowserBuilderオブジェクト`を返し、そのメソッドを介して起動時の設定等を行ってからブラウザを起動します。

```php
builder = BrowserBuilder(BC_MSEDGE)
// ポートを変更
builder.port(9224)
// 起動時オプションを追加
// 拡張機能を無効にする
builder.argument('--disable-extensions')

// startメソッドで起動または再接続
// Browserオブジェクトを返す
msedge = builder.start()
```

設定用のメソッドは`BrowserBuilderオブジェクト`自身を返すため以下のようにメソッドチェーンによる記述もできます。

```php
msedge = BrowserBuilder(BC_MSEDGE) _
    .port(9224) _
    .argument('--disable-extensions') _
    .start()
```

### BrowserBuilderオブジェクトのメソッド

| メソッド |  引数  |                           捕捉                           |               再接続時               |
|----------|--------|----------------------------------------------------------|--------------------------------------|
| port     | 数値   | ブラウザのデバッギングポートを変更                       | このポート番号のブラウザを対象とする |
| headless | 真偽値 | TRUEでヘッドレスブラウザを開く                           | 無視されます                         |
| private  | 真偽値 | TRUEでブラウザをプライベートモードで開く                 | 無視されます                         |
| profile  | 文字列 | プロファイルを保存するパスを指定                         | 無視されます                         |
| argument | 文字列 | 起動時オプションを追加する、複数追加する場合は複数回呼ぶ | 無視されます                         |
| start    | なし   | ブラウザを起動しBrowserオブジェクトを返します            |                                      |


# Browserオブジェクト

Browserオブジェクトは以下のプロパティ及びメソッドを持ちます。

|  名前 |    種別    |                               詳細                               |
|-------|------------|------------------------------------------------------------------|
| count | プロパティ | 操作可能なタブの数を返します                                     |
| tabs  | プロパティ | 操作可能なタブの`TabWindowオブジェクト`を配列で返します          |
| close | メソッド   | ブラウザを閉じます                                               |
| new   | メソッド   | 指定したURLを新しいタブで開き、`TabWindowオブジェクト`を返します |
| id    | メソッド   | ブラウザのウィンドウIDを返します                                 |


## タブの取得

`count`でタブの数を、`tabs`でタブそのもののオブジェクトの配列を得られます。

```php
if browser.count > 0 then
    tab = browser.tabs[0]
endif
```

また、ブラウザオブジェクトに直接インデックスすると`tabs`と同様に動作します。

```php
if browser.count > 0 then
    tab = browser[0]
endif
```

`new`メソッドで新たにタブを開くこともできます。

```php
dim tab
tabs = browser.tabs
if length(tabs) > 0 then
    tab = tabs[0]
else
    // 操作可能なタブがない場合は新しく開く
    tab = browser.new("about:blank")
endif
```

:::note warn
#### タブ一覧取得は重い
`count`および`tabs`プロパティを呼ぶとその都度ブラウザからタブ一覧を取得しているのですが、この処理はレスポンスがとても悪く数秒かかる場合があります。これはChrome Devtools Protocolによるもので現時点では改善の余地がありません。
:::

## ウィンドウIDの取得

ブラウザのウィンドウIDを使ってウィンドウ操作関数を実行できます。

```php
ctrlwin(browser.id(), ACTIVATE)
```

# TabWindowオブジェクト

`TabWindowオブジェクト`を介してタブを操作します。

:::note warn
#### 初回のプロパティやメソッド呼び出しが遅い場合がある

初めてTabWindowのプロパティまたはメソッドを呼び出す際にブラウザのタブに対してWebSocketのセッションを張ります。その接続が確立されるまでに時間がかかる場合があります。
:::


## プロパティ

| プロパティ |      型      |                               詳細                               |
|------------|--------------|------------------------------------------------------------------|
| document   | RemoteObject | ウェブページの`window.document`に相当するオブジェクトを返します  |

## メソッド


|   メソッド  |      引数      | 戻り値 |                                    詳細                                    |
|-------------|----------------|--------|----------------------------------------------------------------------------|
| navigate    | 文字列         | 真偽値 | 指定したURLを開く                                                          |
| reload      | 真偽値         | 真偽値 | ページをリロードします、引数TRUEでキャッシュを無視します                   |
| wait        | 数値           | 真偽値 | ページの読み込み完了まで最大で指定秒数まで待機します、デフォルトは10秒です |
| activate    | なし           | なし   | タブをアクティブにします                                                   |
| close       | なし           | なし   | タブを閉じます                                                             |
| dialog      | 真偽値, 文字列 | なし   | alert, confirm, prompt等のダイアログを処理します                           |
| leftClick   | 数値, 数値     | なし   | 指定座標に左マウスクリックイベントを送ります                               |
| rightClick  | 数値, 数値     | なし   | 指定座標に右マウスクリックイベントを送ります                               |
| middleClick | 数値, 数値     | なし   | 指定座標に中央マウスクリックイベントを送ります                             |
| eval        | 文字列         | ※      | JavaScriptの式を評価しその結果を返します                                   |
## navigate, reload, wait

`navigate`および`reload`メソッドは`wait`メソッドと同様の待機処理を行います(最大で10秒待機)。そのためこれらのメソッド呼び出し時には`wait`メソッドはほぼ不要です。
`wait`メソッドが必要になるのはリンクをクリックし別のページが開かれた場合などです。

## eval

JavaScriptの式を評価します。これにより

- `new`キーワードで新たなオブジェクトを作る
- コールバック用のJavaScript関数を作る

といったことが可能になります。

# RemoteObject


`RemoteObject`はDOMのオブジェクトに相当するオブジェクトです。`RemoteObject`からはそのオブジェクトの実体が持つメソッドやプロパティにアクセスできます。`TabWindowオブジェクト`の`document`プロパティは`document`オブジェクトを返すため、例えば`querySelector`を呼び出すことができます。

```php
tab = browser[0]
document = tab.document
element = document.querySelector("#hoge")
```

`RemoteObject`のプロパティやメソッドが返す値は、プリミティブな値であればUWSCRの値に変換され、それ以外のオブジェクトはすべて`RemoteObject`になります。

`RemoteObject`がどのようなオブジェクトであるかは`RemoteObjectType`関数で調べることができます。

```php
print RemoteObjectType(browser[0].document)
```


# サンプルコード

Seleniumテストページを操作するサンプルコードを掲載します。

```php

// ブラウザを開く
chrome = BrowserControl(BC_CHROME)
// ブラウザをアクティブにする
ctrlwin(chrome.id(), ACTIVATE)

// 新しいタブでSeleniumのテストページを開く
tab = chrome.new('http://example.selenium.jp/reserveApp_Renewal/')
// ドキュメントを取得しておく
document = tab.document

// 宿泊日を入力
// 3日後の日付を得る
date = format(gettime(3, , G_OFFSET_DAYS), '%Y/%m/%d')
// 日付を入力
document.querySelector('#datePick').value = date
document.querySelector('#reserve_year').value = G_TIME_YY4
document.querySelector('#reserve_month').value = G_TIME_MM2
document.querySelector('#reserve_day').value = G_TIME_DD2

// 宿泊日数を選択
reserve_term = 2
document.querySelector("#reserve_term option[value='<#reserve_term>']").selected = TRUE

// 人数を選択
headcount = 5
document.querySelector("#headcount option[value='<#headcount>']").selected = TRUE

// プラン選択
// お得な観光プランをチェック
document.querySelector('#plan_b').checked = TRUE

// 名前入力
document.querySelector('#guestname').value = "おなまえ"

// 利用規約に同意して次へ をクリック
document.querySelector('#agree_and_goto_next').click()

// 読み込み完了を待つ
tab.wait()
// ページを移動したのでdocumentを取得しなおす
document = tab.document

// 合計金額を得る
price = document.querySelector('#price').textContent

// 確定ボタンを押す
document.querySelector('#commit').click()

msgbox("宿泊費用は<#price>円でした")

// タブを閉じる
tab.close()
```