[WTF] Add environment variable helpers
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitGLib / TestAutomationSession.cpp
1 /*
2  * Copyright (C) 2017 Igalia S.L.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2,1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21
22 #include "TestMain.h"
23 #include <gio/gio.h>
24 #include <wtf/Environment.h>
25 #include <wtf/UUID.h>
26 #include <wtf/text/StringBuilder.h>
27
28 class AutomationTest: public Test {
29 public:
30     MAKE_GLIB_TEST_FIXTURE(AutomationTest);
31
32     AutomationTest()
33         : m_mainLoop(adoptGRef(g_main_loop_new(nullptr, TRUE)))
34     {
35         g_dbus_connection_new_for_address("tcp:host=127.0.0.1,port=2229",
36             G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, nullptr, nullptr, [](GObject*, GAsyncResult* result, gpointer userData) {
37                 GRefPtr<GDBusConnection> connection = adoptGRef(g_dbus_connection_new_for_address_finish(result, nullptr));
38                 static_cast<AutomationTest*>(userData)->setConnection(WTFMove(connection));
39             }, this);
40         g_main_loop_run(m_mainLoop.get());
41     }
42
43     ~AutomationTest()
44     {
45     }
46
47     struct Target {
48         Target() = default;
49         Target(guint64 id, CString name, bool isPaired)
50             : id(id)
51             , name(name)
52             , isPaired(isPaired)
53         {
54         }
55
56         guint64 id { 0 };
57         CString name;
58         bool isPaired { false };
59     };
60
61     const GDBusInterfaceVTable s_interfaceVTable = {
62         // method_call
63         [](GDBusConnection* connection, const gchar* sender, const gchar* objectPath, const gchar* interfaceName, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) {
64             auto* test = static_cast<AutomationTest*>(userData);
65             if (!g_strcmp0(methodName, "SetTargetList")) {
66                 guint64 connectionID;
67                 GUniqueOutPtr<GVariantIter> iter;
68                 g_variant_get(parameters, "(ta(tsssb))", &connectionID, &iter.outPtr());
69                 guint64 targetID;
70                 const char* type;
71                 const char* name;
72                 const char* dummy;
73                 gboolean isPaired;
74                 while (g_variant_iter_loop(iter.get(), "(t&s&s&sb)", &targetID, &type, &name, &dummy, &isPaired)) {
75                     if (!g_strcmp0(type, "Automation")) {
76                         test->setTarget(connectionID, Target(targetID, name, isPaired));
77                         break;
78                     }
79                 }
80                 g_dbus_method_invocation_return_value(invocation, nullptr);
81             } else if (!g_strcmp0(methodName, "SendMessageToFrontend")) {
82                 guint64 connectionID, targetID;
83                 const char* message;
84                 g_variant_get(parameters, "(tt&s)", &connectionID, &targetID, &message);
85                 test->receivedMessage(connectionID, targetID, message);
86                 g_dbus_method_invocation_return_value(invocation, nullptr);
87             }
88         },
89         // get_property
90         nullptr,
91         // set_property
92         nullptr,
93         // padding
94         { 0 }
95     };
96
97     void registerDBusObject()
98     {
99         static const char introspectionXML[] =
100             "<node>"
101             "  <interface name='org.webkit.RemoteInspectorClient'>"
102             "    <method name='SetTargetList'>"
103             "      <arg type='t' name='connectionID' direction='in'/>"
104             "      <arg type='a(tsssb)' name='list' direction='in'/>"
105             "    </method>"
106             "    <method name='SendMessageToFrontend'>"
107             "      <arg type='t' name='connectionID' direction='in'/>"
108             "      <arg type='t' name='target' direction='in'/>"
109             "      <arg type='s' name='message' direction='in'/>"
110             "    </method>"
111             "  </interface>"
112             "</node>";
113         static GDBusNodeInfo* introspectionData = nullptr;
114         if (!introspectionData)
115             introspectionData = g_dbus_node_info_new_for_xml(introspectionXML, nullptr);
116         g_dbus_connection_register_object(m_connection.get(), "/org/webkit/RemoteInspectorClient", introspectionData->interfaces[0], &s_interfaceVTable, this, nullptr, nullptr);
117     }
118
119     void setConnection(GRefPtr<GDBusConnection>&& connection)
120     {
121         g_assert_true(G_IS_DBUS_CONNECTION(connection.get()));
122         m_connection = WTFMove(connection);
123         registerDBusObject();
124         g_main_loop_quit(m_mainLoop.get());
125     }
126
127     void setTarget(guint64 connectionID, Target&& target)
128     {
129         bool newConnection = !m_connectionID;
130         bool wasPaired = m_target.isPaired;
131         m_connectionID = connectionID;
132         m_target = WTFMove(target);
133         if (newConnection || (!wasPaired && m_target.isPaired))
134             g_main_loop_quit(m_mainLoop.get());
135     }
136
137     void receivedMessage(guint64 connectionID, guint64 targetID, const char* message)
138     {
139         g_assert_cmpuint(connectionID, ==, m_connectionID);
140         g_assert_cmpuint(targetID, ==, m_target.id);
141         m_message = message;
142         g_main_loop_quit(m_mainLoop.get());
143     }
144
145     void sendCommandToBackend(const String& command, const String& parameters = String())
146     {
147         static long sequenceID = 0;
148         StringBuilder messageBuilder;
149         messageBuilder.appendLiteral("{\"id\":");
150         messageBuilder.appendNumber(++sequenceID);
151         messageBuilder.appendLiteral(",\"method\":\"Automation.");
152         messageBuilder.append(command);
153         messageBuilder.append('"');
154         if (!parameters.isNull()) {
155             messageBuilder.appendLiteral(",\"params\":");
156             messageBuilder.append(parameters);
157         }
158         messageBuilder.append('}');
159         g_dbus_connection_call(m_connection.get(), nullptr, "/org/webkit/Inspector", "org.webkit.Inspector",
160             "SendMessageToBackend", g_variant_new("(tts)", m_connectionID, m_target.id, messageBuilder.toString().utf8().data()),
161             nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, nullptr, nullptr, nullptr);
162     }
163
164     static WebKitWebView* createWebViewCallback(WebKitAutomationSession* session, AutomationTest* test)
165     {
166         test->m_createWebViewWasCalled = true;
167         return test->m_webViewForAutomation;
168     }
169
170     void automationStarted(WebKitAutomationSession* session)
171     {
172         m_session = session;
173         assertObjectIsDeletedWhenTestFinishes(G_OBJECT(m_session));
174         g_assert_null(webkit_automation_session_get_application_info(session));
175         WebKitApplicationInfo* info = webkit_application_info_new();
176         webkit_application_info_set_name(info, "AutomationTestBrowser");
177         webkit_application_info_set_version(info, WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION);
178         webkit_automation_session_set_application_info(session, info);
179         webkit_application_info_unref(info);
180         g_assert_true(webkit_automation_session_get_application_info(session) == info);
181     }
182
183     static void automationStartedCallback(WebKitWebContext* webContext, WebKitAutomationSession* session, AutomationTest* test)
184     {
185         g_assert_true(webContext == test->m_webContext.get());
186         g_assert_true(WEBKIT_IS_AUTOMATION_SESSION(session));
187         test->automationStarted(session);
188     }
189
190     static GUniquePtr<char> toVersionString(unsigned major, unsigned minor, unsigned micro)
191     {
192         if (!micro && !minor)
193             return GUniquePtr<char>(g_strdup_printf("%u", major));
194
195         if (!micro)
196             return GUniquePtr<char>(g_strdup_printf("%u.%u", major, minor));
197
198         return GUniquePtr<char>(g_strdup_printf("%u.%u.%u", major, minor, micro));
199     }
200
201     WebKitAutomationSession* requestSession(const char* sessionID)
202     {
203         auto signalID = g_signal_connect(m_webContext.get(), "automation-started", G_CALLBACK(automationStartedCallback), this);
204         g_dbus_connection_call(m_connection.get(), nullptr, "/org/webkit/Inspector", "org.webkit.Inspector",
205             "StartAutomationSession", g_variant_new("(sa{sv})", sessionID, nullptr), nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, nullptr,
206             [](GObject* source, GAsyncResult* result, gpointer userData) {
207                 auto* test = static_cast<AutomationTest*>(userData);
208                 if (!test->m_session)
209                     return;
210
211                 GRefPtr<GVariant> capabilities = adoptGRef(g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, nullptr));
212                 g_assert_nonnull(capabilities.get());
213                 const char* browserName;
214                 const char* browserVersion;
215                 g_variant_get(capabilities.get(), "(&s&s)", &browserName, &browserVersion);
216                 g_assert_cmpstr(browserName, ==, "AutomationTestBrowser");
217                 GUniquePtr<char> versionString = toVersionString(WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION);
218                 g_assert_cmpstr(browserVersion, ==, versionString.get());
219             }, this
220         );
221         auto timeoutID = g_timeout_add(1000, [](gpointer userData) -> gboolean {
222             g_main_loop_quit(static_cast<GMainLoop*>(userData));
223             return G_SOURCE_REMOVE;
224         }, m_mainLoop.get());
225         g_main_loop_run(m_mainLoop.get());
226         if (!m_connectionID)
227             m_session = nullptr;
228         if (m_session && m_connectionID)
229             g_source_remove(timeoutID);
230         g_signal_handler_disconnect(m_webContext.get(), signalID);
231         return m_session;
232     }
233
234     void setupIfNeeded()
235     {
236         if (m_target.isPaired)
237             return;
238         g_assert_cmpuint(m_target.id, !=, 0);
239         g_dbus_connection_call(m_connection.get(), nullptr, "/org/webkit/Inspector", "org.webkit.Inspector",
240             "Setup", g_variant_new("(tt)", m_connectionID, m_target.id), nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, nullptr, nullptr, nullptr);
241         g_main_loop_run(m_mainLoop.get());
242         g_assert_true(m_target.isPaired);
243     }
244
245     bool createTopLevelBrowsingContext(WebKitWebView* webView)
246     {
247         setupIfNeeded();
248         m_webViewForAutomation = webView;
249         m_createWebViewWasCalled = false;
250         m_message = CString();
251         auto signalID = g_signal_connect(m_session, "create-web-view", G_CALLBACK(createWebViewCallback), this);
252         sendCommandToBackend("createBrowsingContext");
253         g_main_loop_run(m_mainLoop.get());
254         g_signal_handler_disconnect(m_session, signalID);
255         g_assert_true(m_createWebViewWasCalled);
256         g_assert_false(m_message.isNull());
257         m_webViewForAutomation = nullptr;
258
259         if (strstr(m_message.data(), "The remote session failed to create a new browsing context"))
260             return false;
261         if (strstr(m_message.data(), "handle"))
262             return true;
263         return false;
264     }
265
266     GRefPtr<GMainLoop> m_mainLoop;
267     GRefPtr<GDBusConnection> m_connection;
268     WebKitAutomationSession* m_session;
269     guint64 m_connectionID { 0 };
270     Target m_target;
271
272     WebKitWebView* m_webViewForAutomation { nullptr };
273     bool m_createWebViewWasCalled { false };
274     CString m_message;
275 };
276
277 static void testAutomationSessionRequestSession(AutomationTest* test, gconstpointer)
278 {
279     String sessionID = createCanonicalUUIDString();
280     // WebKitAutomationSession::automation-started is never emitted if automation is not enabled.
281     g_assert_false(webkit_web_context_is_automation_allowed(test->m_webContext.get()));
282     auto* session = test->requestSession(sessionID.utf8().data());
283     g_assert_null(session);
284
285     webkit_web_context_set_automation_allowed(test->m_webContext.get(), TRUE);
286     g_assert_true(webkit_web_context_is_automation_allowed(test->m_webContext.get()));
287
288     // There can't be more than one context with automation enabled
289     GRefPtr<WebKitWebContext> otherContext = adoptGRef(webkit_web_context_new());
290     test->removeLogFatalFlag(G_LOG_LEVEL_WARNING);
291     webkit_web_context_set_automation_allowed(otherContext.get(), TRUE);
292     test->addLogFatalFlag(G_LOG_LEVEL_WARNING);
293     g_assert_false(webkit_web_context_is_automation_allowed(otherContext.get()));
294
295     session = test->requestSession(sessionID.utf8().data());
296     g_assert_cmpstr(webkit_automation_session_get_id(session), ==, sessionID.utf8().data());
297     g_assert_cmpuint(test->m_target.id, >, 0);
298     ASSERT_CMP_CSTRING(test->m_target.name, ==, sessionID.utf8());
299     g_assert_false(test->m_target.isPaired);
300
301     // Will fail to create a browsing context when not creating a web view (or not handling the signal).
302     g_assert_false(test->createTopLevelBrowsingContext(nullptr));
303
304     // Will also fail if the web view is not controlled by automation.
305     auto webView = Test::adoptView(Test::createWebView(test->m_webContext.get()));
306     g_assert_false(webkit_web_view_is_controlled_by_automation(webView.get()));
307     g_assert_false(test->createTopLevelBrowsingContext(webView.get()));
308
309     // And will work with a proper web view.
310     webView = Test::adoptView(g_object_new(WEBKIT_TYPE_WEB_VIEW,
311 #if PLATFORM(WPE)
312         "backend", Test::createWebViewBackend(),
313 #endif
314         "web-context", test->m_webContext.get(),
315         "is-controlled-by-automation", TRUE,
316         nullptr));
317     g_assert_true(webkit_web_view_is_controlled_by_automation(webView.get()));
318     g_assert_true(test->createTopLevelBrowsingContext(webView.get()));
319
320     webkit_web_context_set_automation_allowed(test->m_webContext.get(), FALSE);
321 }
322
323 static void testAutomationSessionApplicationInfo(Test* test, gconstpointer)
324 {
325     WebKitApplicationInfo* info = webkit_application_info_new();
326     g_assert_cmpstr(webkit_application_info_get_name(info), ==, g_get_prgname());
327     webkit_application_info_set_name(info, "WebKitGTKBrowser");
328     g_assert_cmpstr(webkit_application_info_get_name(info), ==, "WebKitGTKBrowser");
329     webkit_application_info_set_name(info, nullptr);
330     g_assert_cmpstr(webkit_application_info_get_name(info), ==, g_get_prgname());
331
332     guint64 major, minor, micro;
333     webkit_application_info_get_version(info, &major, nullptr, nullptr);
334     g_assert_cmpuint(major, ==, 0);
335     webkit_application_info_set_version(info, 1, 2, 3);
336     webkit_application_info_get_version(info, &major, &minor, &micro);
337     g_assert_cmpuint(major, ==, 1);
338     g_assert_cmpuint(minor, ==, 2);
339     g_assert_cmpuint(micro, ==, 3);
340
341     webkit_application_info_unref(info);
342 }
343
344
345 void beforeAll()
346 {
347     Environment::set("WEBKIT_INSPECTOR_SERVER", "127.0.0.1:2229");
348
349     AutomationTest::add("WebKitAutomationSession", "request-session", testAutomationSessionRequestSession);
350     Test::add("WebKitAutomationSession", "application-info", testAutomationSessionApplicationInfo);
351 }
352
353 void afterAll()
354 {
355     g_unsetenv("WEBKIT_INSPECTOR_SERVER");
356 }