Changeset 253030 in webkit


Ignore:
Timestamp:
Dec 3, 2019 2:03:32 AM (4 years ago)
Author:
Carlos Garcia Campos
Message:

WebDriver: handle elements of type file in send keys command
https://bugs.webkit.org/show_bug.cgi?id=188514

Reviewed by Brian Burg.

Source/WebCore:

Export symbols now used by WebKit::WebAutomationSessionProxy.

  • html/HTMLInputElement.h:

Source/WebDriver:

Handle the case of inpout element being a file upload in send keys command.

  • Capabilities.h: Add strictFileInteractability.
  • Session.cpp:

(WebDriver::Session::elementIsFileUpload): Helper to check if the given element is a file upload and whether
it's multiple or not.
(WebDriver::Session::parseElementIsFileUploadResult): Parse the result of elementIsFileUpload().
(WebDriver::Session::elementClick): If the element is a file upload, fail with invalid argument error.
(WebDriver::Session::setInputFileUploadFiles): Send setFilesForInputFileUpload message to the browser with the
selected files.
(WebDriver::Session::elementSendKeys): If the element is a file upload, call setInputFileUploadFiles()
instead. Also handle the strictFileInteractability capability to decide whether to focus and check
interactability on the input element or not.

  • Session.h:
  • WebDriverService.cpp:

(WebDriver::WebDriverService::parseCapabilities const): Handle strictFileInteractability.
(WebDriver::WebDriverService::validatedCapabilities const): Ditto.
(WebDriver::WebDriverService::matchCapabilities const): Ditto.
(WebDriver::WebDriverService::createSession): Ditto.

Source/WebKit:

Add setFilesForInputFileUpload method to Automation. It's like setFilesToSelectForFileUpload, but it works
differently, so I'm keeping both to not break safaridriver. The new one simply sends the file list to the web
process to be set on the input element, instead of saving the file list, wait for the driver to trigger the open
panel, intercept and complete the open panel request and send a dismiss open panel event to the driver.

  • UIProcess/Automation/Automation.json: Add setFilesForInputFileUpload.
  • UIProcess/Automation/WebAutomationSession.cpp:

(WebKit::WebAutomationSession::setFilesForInputFileUpload): Send SetFilesForInputFileUpload message to the web process.

  • UIProcess/Automation/WebAutomationSession.h:
  • WebProcess/Automation/WebAutomationSessionProxy.cpp:

(WebKit::WebAutomationSessionProxy::setFilesForInputFileUpload): Create a FileList with the received paths and
call HTMLInputElement::setFiles() on the given element.

  • WebProcess/Automation/WebAutomationSessionProxy.h:
  • WebProcess/Automation/WebAutomationSessionProxy.messages.in: Add SetFilesForInputFileUpload message.

WebDriverTests:

Remove expectations for tests that are now passing.

Location:
trunk
Files:
16 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r253028 r253030  
     12019-12-03  Carlos Garcia Campos  <cgarcia@igalia.com>
     2
     3        WebDriver: handle elements of type file in send keys command
     4        https://bugs.webkit.org/show_bug.cgi?id=188514
     5
     6        Reviewed by Brian Burg.
     7
     8        Export symbols now used by WebKit::WebAutomationSessionProxy.
     9
     10        * html/HTMLInputElement.h:
     11
    1122019-12-03  Carlos Garcia Campos  <cgarcia@igalia.com>
    213
  • trunk/Source/WebCore/html/HTMLInputElement.h

    r251110 r253030  
    119119
    120120    WEBCORE_EXPORT bool isEmailField() const;
    121     bool isFileUpload() const;
     121    WEBCORE_EXPORT bool isFileUpload() const;
    122122    bool isImageButton() const;
    123123    WEBCORE_EXPORT bool isNumberField() const;
     
    236236    unsigned effectiveMaxLength() const;
    237237
    238     bool multiple() const;
     238    WEBCORE_EXPORT bool multiple() const;
    239239
    240240    bool isAutoFilled() const { return m_isAutoFilled; }
  • trunk/Source/WebDriver/Capabilities.h

    r252323 r253030  
    7171    Optional<String> platformName;
    7272    Optional<bool> acceptInsecureCerts;
     73    Optional<bool> strictFileInteractability;
    7374    Optional<bool> setWindowRect;
    7475    Optional<Timeouts> timeouts;
  • trunk/Source/WebDriver/ChangeLog

    r253029 r253030  
     12019-12-03  Carlos Garcia Campos  <cgarcia@igalia.com>
     2
     3        WebDriver: handle elements of type file in send keys command
     4        https://bugs.webkit.org/show_bug.cgi?id=188514
     5
     6        Reviewed by Brian Burg.
     7
     8        Handle the case of inpout element being a file upload in send keys command.
     9
     10        * Capabilities.h: Add strictFileInteractability.
     11        * Session.cpp:
     12        (WebDriver::Session::elementIsFileUpload): Helper to check if the given element is a file upload and whether
     13        it's multiple or not.
     14        (WebDriver::Session::parseElementIsFileUploadResult): Parse the result of elementIsFileUpload().
     15        (WebDriver::Session::elementClick): If the element is a file upload, fail with invalid argument error.
     16        (WebDriver::Session::setInputFileUploadFiles): Send setFilesForInputFileUpload message to the browser with the
     17        selected files.
     18        (WebDriver::Session::elementSendKeys): If the element is a file upload, call setInputFileUploadFiles()
     19        instead. Also handle the strictFileInteractability capability to decide whether to focus and check
     20        interactability on the input element or not.
     21        * Session.h:
     22        * WebDriverService.cpp:
     23        (WebDriver::WebDriverService::parseCapabilities const): Handle strictFileInteractability.
     24        (WebDriver::WebDriverService::validatedCapabilities const): Ditto.
     25        (WebDriver::WebDriverService::matchCapabilities const): Ditto.
     26        (WebDriver::WebDriverService::createSession): Ditto.
     27
    1282019-12-03  Carlos Garcia Campos  <cgarcia@igalia.com>
    229
  • trunk/Source/WebDriver/Session.cpp

    r253029 r253030  
    3131#include "WebDriverAtoms.h"
    3232#include <wtf/CryptographicallyRandomNumber.h>
     33#include <wtf/FileSystem.h>
    3334#include <wtf/HashSet.h>
    3435#include <wtf/HexNumber.h>
     
    15291530}
    15301531
     1532void Session::elementIsFileUpload(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
     1533{
     1534    RefPtr<JSON::Array> arguments = JSON::Array::create();
     1535    arguments->pushString(createElement(elementID)->toJSONString());
     1536
     1537    static const char isFileUploadScript[] =
     1538        "function(element) {"
     1539        "    if (element.tagName.toLowerCase() === 'input' && element.type === 'file')"
     1540        "        return { 'fileUpload': true, 'multiple': element.hasAttribute('multiple') };"
     1541        "    return { 'fileUpload': false };"
     1542        "}";
     1543
     1544    RefPtr<JSON::Object> parameters = JSON::Object::create();
     1545    parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
     1546    if (m_currentBrowsingContext)
     1547        parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
     1548    parameters->setString("function"_s, isFileUploadScript);
     1549    parameters->setArray("arguments"_s, WTFMove(arguments));
     1550    m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
     1551        if (response.isError || !response.responseObject) {
     1552            completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
     1553            return;
     1554        }
     1555        String valueString;
     1556        if (!response.responseObject->getString("result"_s, valueString)) {
     1557            completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
     1558            return;
     1559        }
     1560        RefPtr<JSON::Value> resultValue;
     1561        if (!JSON::Value::parseJSON(valueString, resultValue)) {
     1562            completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
     1563            return;
     1564        }
     1565        completionHandler(CommandResult::success(WTFMove(resultValue)));
     1566    });
     1567}
     1568
     1569Optional<Session::FileUploadType> Session::parseElementIsFileUploadResult(const RefPtr<JSON::Value>& resultValue)
     1570{
     1571    RefPtr<JSON::Object> result;
     1572    if (!resultValue->asObject(result))
     1573        return WTF::nullopt;
     1574
     1575    bool isFileUpload;
     1576    if (!result->getBoolean("fileUpload"_s, isFileUpload) || !isFileUpload)
     1577        return WTF::nullopt;
     1578
     1579    bool multiple;
     1580    if (!result->getBoolean("multiple"_s, multiple) || !multiple)
     1581        return FileUploadType::Single;
     1582
     1583    return FileUploadType::Multiple;
     1584}
     1585
    15311586void Session::selectOptionElement(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
    15321587{
     
    15561611            return;
    15571612        }
    1558         OptionSet<ElementLayoutOption> options = { ElementLayoutOption::ScrollIntoViewIfNeeded, ElementLayoutOption::UseViewportCoordinates };
    1559         computeElementLayout(elementID, options, [this, protectedThis = protectedThis.copyRef(), elementID, completionHandler = WTFMove(completionHandler)](Optional<Rect>&& rect, Optional<Point>&& inViewCenter, bool isObscured, RefPtr<JSON::Object>&& error) mutable {
    1560             if (!rect || error) {
    1561                 completionHandler(CommandResult::fail(WTFMove(error)));
    1562                 return;
    1563             }
    1564             if (isObscured) {
    1565                 completionHandler(CommandResult::fail(CommandResult::ErrorCode::ElementClickIntercepted));
    1566                 return;
    1567             }
    1568             if (!inViewCenter) {
    1569                 completionHandler(CommandResult::fail(CommandResult::ErrorCode::ElementNotInteractable));
    1570                 return;
    1571             }
    1572 
    1573             getElementTagName(elementID, [this, elementID, inViewCenter = WTFMove(inViewCenter), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
    1574                 bool isOptionElement = false;
    1575                 if (!result.isError()) {
    1576                     String tagName;
    1577                     if (result.result()->asString(tagName))
    1578                         isOptionElement = tagName == "option";
     1613        elementIsFileUpload(elementID, [this, protectedThis = protectedThis.copyRef(), elementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
     1614            if (result.isError()) {
     1615                completionHandler(WTFMove(result));
     1616                return;
     1617            }
     1618
     1619            if (parseElementIsFileUploadResult(result.result())) {
     1620                completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
     1621                return;
     1622            }
     1623            OptionSet<ElementLayoutOption> options = { ElementLayoutOption::ScrollIntoViewIfNeeded, ElementLayoutOption::UseViewportCoordinates };
     1624            computeElementLayout(elementID, options, [this, protectedThis = protectedThis.copyRef(), elementID, completionHandler = WTFMove(completionHandler)](Optional<Rect>&& rect, Optional<Point>&& inViewCenter, bool isObscured, RefPtr<JSON::Object>&& error) mutable {
     1625                if (!rect || error) {
     1626                    completionHandler(CommandResult::fail(WTFMove(error)));
     1627                    return;
    15791628                }
    1580 
    1581                 Function<void (CommandResult&&)> continueAfterClickFunction = [this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
    1582                     if (result.isError()) {
    1583                         completionHandler(WTFMove(result));
    1584                         return;
     1629                if (isObscured) {
     1630                    completionHandler(CommandResult::fail(CommandResult::ErrorCode::ElementClickIntercepted));
     1631                    return;
     1632                }
     1633                if (!inViewCenter) {
     1634                    completionHandler(CommandResult::fail(CommandResult::ErrorCode::ElementNotInteractable));
     1635                    return;
     1636                }
     1637
     1638                getElementTagName(elementID, [this, elementID, inViewCenter = WTFMove(inViewCenter), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
     1639                    bool isOptionElement = false;
     1640                    if (!result.isError()) {
     1641                        String tagName;
     1642                        if (result.result()->asString(tagName))
     1643                            isOptionElement = tagName == "option";
    15851644                    }
    15861645
    1587                     waitForNavigationToComplete(WTFMove(completionHandler));
    1588                 };
    1589                 if (isOptionElement)
    1590                     selectOptionElement(elementID, WTFMove(continueAfterClickFunction));
    1591                 else
    1592                     performMouseInteraction(inViewCenter.value().x, inViewCenter.value().y, MouseButton::Left, MouseInteraction::SingleClick, WTFMove(continueAfterClickFunction));
     1646                    Function<void (CommandResult&&)> continueAfterClickFunction = [this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
     1647                        if (result.isError()) {
     1648                            completionHandler(WTFMove(result));
     1649                            return;
     1650                        }
     1651
     1652                        waitForNavigationToComplete(WTFMove(completionHandler));
     1653                    };
     1654                    if (isOptionElement)
     1655                        selectOptionElement(elementID, WTFMove(continueAfterClickFunction));
     1656                    else
     1657                        performMouseInteraction(inViewCenter.value().x, inViewCenter.value().y, MouseButton::Left, MouseInteraction::SingleClick, WTFMove(continueAfterClickFunction));
     1658                });
    15931659            });
    15941660        });
     
    16961762            });
    16971763        });
     1764    });
     1765}
     1766
     1767void Session::setInputFileUploadFiles(const String& elementID, const String& text, bool multiple, Function<void (CommandResult&&)>&& completionHandler)
     1768{
     1769    Vector<String> files = text.split('\n');
     1770    if (files.isEmpty()) {
     1771        completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
     1772        return;
     1773    }
     1774
     1775    if (!multiple && files.size() != 1) {
     1776        completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
     1777        return;
     1778    }
     1779
     1780    RefPtr<JSON::Array> filenames = JSON::Array::create();
     1781    for (const auto& file : files) {
     1782        if (!FileSystem::fileExists(file)) {
     1783            completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
     1784            return;
     1785        }
     1786        filenames->pushString(file);
     1787    }
     1788
     1789    RefPtr<JSON::Object> parameters = JSON::Object::create();
     1790    parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
     1791    parameters->setString("frameHandle"_s, m_currentBrowsingContext.valueOr(emptyString()));
     1792    parameters->setString("nodeHandle"_s, elementID);
     1793    parameters->setArray("filenames"_s, WTFMove(filenames));
     1794    m_host->sendCommandToBackend("setFilesForInputFileUpload"_s, WTFMove(parameters), [this, protectedThis = makeRef(*this), elementID, completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
     1795        if (response.isError) {
     1796            completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
     1797            return;
     1798        }
     1799
     1800        completionHandler(CommandResult::success());
    16981801    });
    16991802}
     
    18491952            return;
    18501953        }
    1851         // FIXME: move this to an atom.
    1852         static const char focusScript[] =
    1853             "function focus(element) {"
    1854             "    let doc = element.ownerDocument || element;"
    1855             "    let prevActiveElement = doc.activeElement;"
    1856             "    if (element != prevActiveElement && prevActiveElement)"
    1857             "        prevActiveElement.blur();"
    1858             "    element.focus();"
    1859             "    let tagName = element.tagName.toUpperCase();"
    1860             "    if (tagName === 'BODY' || element === document.documentElement)"
    1861             "        return;"
    1862             "    let isTextElement = tagName === 'TEXTAREA' || (tagName === 'INPUT' && element.type === 'text');"
    1863             "    if (isTextElement && element.selectionEnd == 0)"
    1864             "        element.setSelectionRange(element.value.length, element.value.length);"
    1865             "    if (element != doc.activeElement)"
    1866             "        throw {name: 'ElementNotInteractable', message: 'Element is not focusable.'};"
    1867             "}";
    1868 
    1869         RefPtr<JSON::Array> arguments = JSON::Array::create();
    1870         arguments->pushString(createElement(elementID)->toJSONString());
    1871         RefPtr<JSON::Object> parameters = JSON::Object::create();
    1872         parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
    1873         if (m_currentBrowsingContext)
    1874             parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
    1875         parameters->setString("function"_s, focusScript);
    1876         parameters->setArray("arguments"_s, WTFMove(arguments));
    1877         m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), text, completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
    1878             if (response.isError || !response.responseObject) {
    1879                 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
    1880                 return;
    1881             }
    1882 
    1883             unsigned stickyModifiers = 0;
    1884             auto textLength = text.length();
    1885             Vector<KeyboardInteraction> interactions;
    1886             interactions.reserveInitialCapacity(textLength);
    1887             for (unsigned i = 0; i < textLength; ++i) {
    1888                 auto key = text[i];
    1889                 KeyboardInteraction interaction;
    1890                 KeyModifier modifier;
    1891                 auto virtualKey = virtualKeyForKey(key, modifier);
    1892                 if (!virtualKey.isNull()) {
    1893                     interaction.key = virtualKey;
    1894                     if (modifier != KeyModifier::None) {
    1895                         stickyModifiers ^= modifier;
    1896                         if (stickyModifiers & modifier)
    1897                             interaction.type = KeyboardInteractionType::KeyPress;
    1898                         else
    1899                             interaction.type = KeyboardInteractionType::KeyRelease;
     1954        elementIsFileUpload(elementID, [this, protectedThis = protectedThis.copyRef(), elementID, text, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
     1955            if (result.isError()) {
     1956                completionHandler(WTFMove(result));
     1957                return;
     1958            }
     1959
     1960            auto fileUploadType = parseElementIsFileUploadResult(result.result());
     1961            if (!fileUploadType || capabilities().strictFileInteractability.valueOr(false)) {
     1962                // FIXME: move this to an atom.
     1963                static const char focusScript[] =
     1964                    "function focus(element) {"
     1965                    "    let doc = element.ownerDocument || element;"
     1966                    "    let prevActiveElement = doc.activeElement;"
     1967                    "    if (element != prevActiveElement && prevActiveElement)"
     1968                    "        prevActiveElement.blur();"
     1969                    "    element.focus();"
     1970                    "    let tagName = element.tagName.toUpperCase();"
     1971                    "    if (tagName === 'BODY' || element === document.documentElement)"
     1972                    "        return;"
     1973                    "    let isTextElement = tagName === 'TEXTAREA' || (tagName === 'INPUT' && element.type === 'text');"
     1974                    "    if (isTextElement && element.selectionEnd == 0)"
     1975                    "        element.setSelectionRange(element.value.length, element.value.length);"
     1976                    "    if (element != doc.activeElement)"
     1977                    "        throw {name: 'ElementNotInteractable', message: 'Element is not focusable.'};"
     1978                    "}";
     1979
     1980                RefPtr<JSON::Array> arguments = JSON::Array::create();
     1981                arguments->pushString(createElement(elementID)->toJSONString());
     1982                RefPtr<JSON::Object> parameters = JSON::Object::create();
     1983                parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
     1984                if (m_currentBrowsingContext)
     1985                    parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
     1986                parameters->setString("function"_s, focusScript);
     1987                parameters->setArray("arguments"_s, WTFMove(arguments));
     1988                m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), fileUploadType, elementID, text, completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
     1989                    if (response.isError || !response.responseObject) {
     1990                        completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
     1991                        return;
    19001992                    }
    1901                 } else
    1902                     interaction.text = String(&key, 1);
    1903                 interactions.uncheckedAppend(WTFMove(interaction));
    1904             }
    1905 
    1906             // Reset sticky modifiers if needed.
    1907             if (stickyModifiers) {
    1908                 if (stickyModifiers & KeyModifier::Shift)
    1909                     interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Shift"_s) });
    1910                 if (stickyModifiers & KeyModifier::Control)
    1911                     interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Control"_s) });
    1912                 if (stickyModifiers & KeyModifier::Alternate)
    1913                     interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Alternate"_s) });
    1914                 if (stickyModifiers & KeyModifier::Meta)
    1915                     interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Meta"_s) });
    1916             }
    1917 
    1918             performKeyboardInteractions(WTFMove(interactions), WTFMove(completionHandler));
     1993
     1994                    if (fileUploadType) {
     1995                        setInputFileUploadFiles(elementID, text, fileUploadType.value() == FileUploadType::Multiple, WTFMove(completionHandler));
     1996                        return;
     1997                    }
     1998
     1999                    unsigned stickyModifiers = 0;
     2000                    auto textLength = text.length();
     2001                    Vector<KeyboardInteraction> interactions;
     2002                    interactions.reserveInitialCapacity(textLength);
     2003                    for (unsigned i = 0; i < textLength; ++i) {
     2004                        auto key = text[i];
     2005                        KeyboardInteraction interaction;
     2006                        KeyModifier modifier;
     2007                        auto virtualKey = virtualKeyForKey(key, modifier);
     2008                        if (!virtualKey.isNull()) {
     2009                            interaction.key = virtualKey;
     2010                            if (modifier != KeyModifier::None) {
     2011                                stickyModifiers ^= modifier;
     2012                                if (stickyModifiers & modifier)
     2013                                    interaction.type = KeyboardInteractionType::KeyPress;
     2014                                else
     2015                                    interaction.type = KeyboardInteractionType::KeyRelease;
     2016                            }
     2017                        } else
     2018                            interaction.text = String(&key, 1);
     2019                        interactions.uncheckedAppend(WTFMove(interaction));
     2020                    }
     2021
     2022                    // Reset sticky modifiers if needed.
     2023                    if (stickyModifiers) {
     2024                        if (stickyModifiers & KeyModifier::Shift)
     2025                            interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Shift"_s) });
     2026                        if (stickyModifiers & KeyModifier::Control)
     2027                            interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Control"_s) });
     2028                        if (stickyModifiers & KeyModifier::Alternate)
     2029                            interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Alternate"_s) });
     2030                        if (stickyModifiers & KeyModifier::Meta)
     2031                            interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Meta"_s) });
     2032                    }
     2033
     2034                    performKeyboardInteractions(WTFMove(interactions), WTFMove(completionHandler));
     2035                });
     2036            } else {
     2037                setInputFileUploadFiles(elementID, text, fileUploadType.value() == FileUploadType::Multiple, WTFMove(completionHandler));
     2038                return;
     2039            }
    19192040        });
    19202041    });
  • trunk/Source/WebDriver/Session.h

    r253029 r253030  
    170170    void computeElementLayout(const String& elementID, OptionSet<ElementLayoutOption>, Function<void (Optional<Rect>&&, Optional<Point>&&, bool, RefPtr<JSON::Object>&&)>&&);
    171171
     172    void elementIsFileUpload(const String& elementID, Function<void (CommandResult&&)>&&);
     173    enum class FileUploadType { Single, Multiple };
     174    Optional<FileUploadType> parseElementIsFileUploadResult(const RefPtr<JSON::Value>&);
    172175    void selectOptionElement(const String& elementID, Function<void (CommandResult&&)>&&);
     176    void setInputFileUploadFiles(const String& elementID, const String& text, bool multiple, Function<void (CommandResult&&)>&&);
     177    void didSetInputFileUploadFiles(bool wasCancelled);
    173178
    174179    enum class MouseInteraction { Move, Down, Up, SingleClick, DoubleClick };
  • trunk/Source/WebDriver/WebDriverService.cpp

    r252408 r253030  
    483483    if (matchedCapabilities.getObject("proxy"_s, proxy))
    484484        capabilities.proxy = deserializeProxy(*proxy);
     485    bool strictFileInteractability;
     486    if (matchedCapabilities.getBoolean("strictFileInteractability"_s, strictFileInteractability))
     487        capabilities.strictFileInteractability = strictFileInteractability;
    485488    RefPtr<JSON::Object> timeouts;
    486489    if (matchedCapabilities.getObject("timeouts"_s, timeouts))
     
    540543                return nullptr;
    541544            result->setValue(it->key, RefPtr<JSON::Value>(it->value));
     545        } else if (it->key == "strictFileInteractability") {
     546            bool strictFileInteractability;
     547            if (!it->value->asBoolean(strictFileInteractability))
     548                return nullptr;
     549            result->setBoolean(it->key, strictFileInteractability);
    542550        } else if (it->key == "timeouts") {
    543551            RefPtr<JSON::Object> timeouts;
     
    593601    if (platformCapabilities.acceptInsecureCerts)
    594602        matchedCapabilities->setBoolean("acceptInsecureCerts"_s, platformCapabilities.acceptInsecureCerts.value());
     603    if (platformCapabilities.strictFileInteractability)
     604        matchedCapabilities->setBoolean("strictFileInteractability"_s, platformCapabilities.strictFileInteractability.value());
    595605    if (platformCapabilities.setWindowRect)
    596606        matchedCapabilities->setBoolean("setWindowRect"_s, platformCapabilities.setWindowRect.value());
     
    801811            capabilitiesObject->setString("platformName"_s, capabilities.platformName.valueOr(emptyString()));
    802812            capabilitiesObject->setBoolean("acceptInsecureCerts"_s, capabilities.acceptInsecureCerts.valueOr(false));
     813            capabilitiesObject->setBoolean("strictFileInteractability"_s, capabilities.strictFileInteractability.valueOr(false));
    803814            capabilitiesObject->setBoolean("setWindowRect"_s, capabilities.setWindowRect.valueOr(true));
    804815            switch (capabilities.unhandledPromptBehavior.valueOr(UnhandledPromptBehavior::DismissAndNotify)) {
  • trunk/Source/WebKit/ChangeLog

    r253029 r253030  
     12019-12-03  Carlos Garcia Campos  <cgarcia@igalia.com>
     2
     3        WebDriver: handle elements of type file in send keys command
     4        https://bugs.webkit.org/show_bug.cgi?id=188514
     5
     6        Reviewed by Brian Burg.
     7
     8        Add setFilesForInputFileUpload method to Automation. It's like setFilesToSelectForFileUpload, but it works
     9        differently, so I'm keeping both to not break safaridriver. The new one simply sends the file list to the web
     10        process to be set on the input element, instead of saving the file list, wait for the driver to trigger the open
     11        panel, intercept and complete the open panel request and send a dismiss open panel event to the driver.
     12
     13        * UIProcess/Automation/Automation.json: Add setFilesForInputFileUpload.
     14        * UIProcess/Automation/WebAutomationSession.cpp:
     15        (WebKit::WebAutomationSession::setFilesForInputFileUpload): Send SetFilesForInputFileUpload message to the web process.
     16        * UIProcess/Automation/WebAutomationSession.h:
     17        * WebProcess/Automation/WebAutomationSessionProxy.cpp:
     18        (WebKit::WebAutomationSessionProxy::setFilesForInputFileUpload): Create a FileList with the received paths and
     19        call HTMLInputElement::setFiles() on the given element.
     20        * WebProcess/Automation/WebAutomationSessionProxy.h:
     21        * WebProcess/Automation/WebAutomationSessionProxy.messages.in: Add SetFilesForInputFileUpload message.
     22
    1232019-12-03  Carlos Garcia Campos  <cgarcia@igalia.com>
    224
  • trunk/Source/WebKit/UIProcess/Automation/Automation.json

    r245320 r253030  
    625625        },
    626626        {
     627            "name": "setFilesForInputFileUpload",
     628            "description": "Sets the choosen files for the given input file upload element.",
     629            "parameters": [
     630                { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." },
     631                { "name": "frameHandle", "$ref": "FrameHandle", "description": "The handle for the frame that contains the input element." },
     632                { "name": "nodeHandle", "$ref": "NodeHandle", "description": "The handle of the input element." },
     633                { "name": "filenames", "type": "array", "items": { "type": "string" }, "description": "Absolute paths to the choosen files." }
     634            ],
     635            "async": true
     636        },
     637        {
    627638            "name": "getAllCookies",
    628639            "description": "Returns all cookies visible to the specified browsing context.",
  • trunk/Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp

    r251011 r253030  
    12401240}
    12411241
     1242void WebAutomationSession::setFilesForInputFileUpload(const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const JSON::Array& filenames, Ref<SetFilesForInputFileUploadCallback>&& callback)
     1243{
     1244    WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
     1245    if (!page)
     1246        ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
     1247
     1248    bool frameNotFound = false;
     1249    auto frameID = webFrameIDForHandle(frameHandle, frameNotFound);
     1250    if (frameNotFound)
     1251        ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
     1252
     1253    Vector<String> newFileList;
     1254    newFileList.reserveInitialCapacity(filenames.length());
     1255    for (size_t i = 0; i < filenames.length(); ++i) {
     1256        String filename;
     1257        if (!filenames.get(i)->asString(filename))
     1258            ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The parameter 'filenames' contains a non-string value.");
     1259
     1260        newFileList.append(filename);
     1261    }
     1262
     1263    CompletionHandler<void(Optional<String>)> completionHandler = [callback = callback.copyRef()](Optional<String> errorType) mutable {
     1264        if (errorType) {
     1265            callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(*errorType));
     1266            return;
     1267        }
     1268
     1269        callback->sendSuccess();
     1270    };
     1271
     1272    page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::SetFilesForInputFileUpload(page->webPageID(), frameID, nodeHandle, WTFMove(newFileList)), WTFMove(completionHandler));
     1273}
     1274
    12421275static Ref<Inspector::Protocol::Automation::Cookie> buildObjectForCookie(const WebCore::Cookie& cookie)
    12431276{
  • trunk/Source/WebKit/UIProcess/Automation/WebAutomationSession.h

    r249275 r253030  
    192192    void setUserInputForCurrentJavaScriptPrompt(Inspector::ErrorString&, const String& browsingContextHandle, const String& text) override;
    193193    void setFilesToSelectForFileUpload(Inspector::ErrorString&, const String& browsingContextHandle, const JSON::Array& filenames, const JSON::Array* optionalFileContents) override;
     194    void setFilesForInputFileUpload(const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const JSON::Array& filenames, Ref<SetFilesForInputFileUploadCallback>&&) override;
    194195    void getAllCookies(const String& browsingContextHandle, Ref<GetAllCookiesCallback>&&) override;
    195196    void deleteSingleCookie(const String& browsingContextHandle, const String& cookieName, Ref<DeleteSingleCookieCallback>&&) override;
  • trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.cpp

    r252539 r253030  
    4545#include <WebCore/DOMRectList.h>
    4646#include <WebCore/DOMWindow.h>
     47#include <WebCore/File.h>
     48#include <WebCore/FileList.h>
    4749#include <WebCore/Frame.h>
    4850#include <WebCore/FrameTree.h>
     
    5052#include <WebCore/HTMLFrameElement.h>
    5153#include <WebCore/HTMLIFrameElement.h>
     54#include <WebCore/HTMLInputElement.h>
    5255#include <WebCore/HTMLOptGroupElement.h>
    5356#include <WebCore/HTMLOptionElement.h>
     
    683686}
    684687
     688void WebAutomationSessionProxy::setFilesForInputFileUpload(WebCore::PageIdentifier pageID, Optional<WebCore::FrameIdentifier> frameID, String nodeHandle, Vector<String>&& filenames, CompletionHandler<void(Optional<String>)>&& completionHandler)
     689{
     690    WebPage* page = WebProcess::singleton().webPage(pageID);
     691    if (!page) {
     692        String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
     693        completionHandler(windowNotFoundErrorType);
     694        return;
     695    }
     696
     697    WebFrame* frame = frameID ? WebProcess::singleton().webFrame(*frameID) : page->mainWebFrame();
     698    if (!frame || !frame->coreFrame() || !frame->coreFrame()->view()) {
     699        String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound);
     700        completionHandler(frameNotFoundErrorType);
     701        return;
     702    }
     703
     704    WebCore::Element* coreElement = elementForNodeHandle(*frame, nodeHandle);
     705    if (!coreElement || !is<WebCore::HTMLInputElement>(coreElement) || !downcast<WebCore::HTMLInputElement>(*coreElement).isFileUpload()) {
     706        String nodeNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NodeNotFound);
     707        completionHandler(nodeNotFoundErrorType);
     708        return;
     709    }
     710
     711    auto& inputElement = downcast<WebCore::HTMLInputElement>(*coreElement);
     712    Vector<Ref<WebCore::File>> fileObjects;
     713    if (inputElement.multiple()) {
     714        if (auto* files = inputElement.files()) {
     715            for (auto& file : files->files())
     716                fileObjects.append(file.copyRef());
     717        }
     718    }
     719    for (const auto& path : filenames)
     720        fileObjects.append(File::create(path));
     721    inputElement.setFiles(FileList::create(WTFMove(fileObjects)));
     722
     723    completionHandler(WTF::nullopt);
     724}
     725
    685726static WebCore::IntRect snapshotRectForScreenshot(WebPage& page, WebCore::Element* element, bool clipToViewport)
    686727{
  • trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.h

    r251361 r253030  
    7272    void computeElementLayout(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>, String nodeHandle, bool scrollIntoViewIfNeeded, CoordinateSystem, CompletionHandler<void(Optional<String>, WebCore::IntRect, Optional<WebCore::IntPoint>, bool)>&&);
    7373    void selectOptionElement(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>, String nodeHandle, CompletionHandler<void(Optional<String>)>&&);
     74    void setFilesForInputFileUpload(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>, String nodeHandle, Vector<String>&& filenames, CompletionHandler<void(Optional<String>)>&&);
    7475    void takeScreenshot(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>, String nodeHandle, bool scrollIntoViewIfNeeded, bool clipToViewport, uint64_t callbackID);
    7576    void getCookiesForFrame(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>, CompletionHandler<void(Optional<String>, Vector<WebCore::Cookie>)>&&);
  • trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.messages.in

    r252655 r253030  
    3535    SelectOptionElement(WebCore::PageIdentifier pageID, Optional<WebCore::FrameIdentifier> frameID, String nodeHandle) -> (Optional<String> errorType) Async
    3636
     37    SetFilesForInputFileUpload(WebCore::PageIdentifier pageID, Optional<WebCore::FrameIdentifier> frameID, String nodeHandle, Vector<String> filenames) -> (Optional<String> errorType) Async
     38
    3739    TakeScreenshot(WebCore::PageIdentifier pageID, Optional<WebCore::FrameIdentifier> frameID, String nodeHandle, bool scrollIntoViewIfNeeded, bool clipToViewport, uint64_t callbackID)
    3840
  • trunk/WebDriverTests/ChangeLog

    r253029 r253030  
     12019-12-03  Carlos Garcia Campos  <cgarcia@igalia.com>
     2
     3        WebDriver: handle elements of type file in send keys command
     4        https://bugs.webkit.org/show_bug.cgi?id=188514
     5
     6        Reviewed by Brian Burg.
     7
     8        Remove expectations for tests that are now passing.
     9
     10        * TestExpectations.json:
     11
    1122019-12-03  Carlos Garcia Campos  <cgarcia@igalia.com>
    213
  • trunk/WebDriverTests/TestExpectations.json

    r253029 r253030  
    407407        }
    408408    },
    409     "imported/w3c/webdriver/tests/element_click/file_upload.py": {
    410         "subtests": {
    411             "test_file_upload_state": {
    412                 "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188513"}}
    413             }
    414         }
    415     },
    416409    "imported/w3c/webdriver/tests/element_click/interactability.py": {
    417410        "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188545"}},
     
    501494    "imported/w3c/webdriver/tests/element_send_keys/events.py": {
    502495        "subtests": {
    503             "test_file_upload": {
    504                 "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
    505             },
    506496            "test_not_blurred[input]": {
    507497                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188118"}}
     
    512502        }
    513503    },
    514     "imported/w3c/webdriver/tests/element_send_keys/file_upload.py": {
    515         "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}},
    516         "subtests": {
    517             "test_empty_text": {
    518                 "expected": {"all": {"status": ["PASS"]}}
    519             }
    520         }
    521     },
    522504    "imported/w3c/webdriver/tests/element_send_keys/form_controls.py": {
    523505        "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/182331"}},
     
    609591            "test_get_current_url_matches_location": {
    610592                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/180405"}}
    611             }
    612         }
    613     },
    614     "imported/w3c/webdriver/tests/new_session/create_alwaysMatch.py": {
    615         "subtests": {
    616             "test_valid[strictFileInteractability-True]": {
    617                 "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
    618             },
    619             "test_valid[strictFileInteractability-False]": {
    620                 "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
    621             }
    622         }
    623     },
    624     "imported/w3c/webdriver/tests/new_session/create_firstMatch.py": {
    625         "subtests": {
    626             "test_valid[strictFileInteractability-True]": {
    627                 "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
    628             },
    629             "test_valid[strictFileInteractability-False]": {
    630                 "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
    631             }
    632         }
    633     },
    634     "imported/w3c/webdriver/tests/new_session/response.py": {
    635         "subtests": {
    636             "test_capability_type[strictFileInteractability-bool]": {
    637                 "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
    638             },
    639             "test_capability_default_value[strictFileInteractability-False]": {
    640                 "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
    641593            }
    642594        }
Note: See TracChangeset for help on using the changeset viewer.