/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see .
**/
#include "webdriver.h"
#include "zbxcommon.h"
#include "zbxjson.h"
#include "zbxstr.h"
#include "zbxtime.h"
#include "zbxtypes.h"
#include "zbxcurl.h"
#ifdef HAVE_LIBCURL
#define WEBDRIVER_INVALID_SESSIONID_ERROR "invalid session id"
#define WEBDRIVER_ELEMENT_ID "element-6066-11e4-a52e-4f735466cecf"
static size_t curl_write_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
{
size_t r_size = size * nmemb;
zbx_webdriver_t *wd = (zbx_webdriver_t *)userdata;
zbx_str_memcpy_alloc(&wd->data, &wd->data_alloc, &wd->data_offset, (const char *)ptr, r_size);
return r_size;
}
static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
{
size_t r_size = size * nmemb;
zbx_webdriver_t *wd = (zbx_webdriver_t *)userdata;
zbx_strncpy_alloc(&wd->headers_in, &wd->headers_in_alloc, &wd->headers_in_offset, (const char *)ptr, r_size);
return r_size;
}
/******************************************************************************
* *
* Purpose: utility function for checking curl attribute setting errors *
* *
******************************************************************************/
static int webdriver_curl_check_error(CURLcode err, CURLoption opt, char **error)
{
if (CURLE_OK == err)
return SUCCEED;
*error = zbx_dsprintf(NULL, "cannot set cURL option %u: %s.", opt, curl_easy_strerror(err));
return FAIL;
}
#define CURL_SETOPT(handle, option, value, error) \
webdriver_curl_check_error(curl_easy_setopt(handle, option, value), option, error)
/******************************************************************************
* *
* Purpose: get value returned by webdriver {"value":} *
* *
* Parameters: response - [IN] webdriver response *
* jp - [OUT] value contents *
* simple - start/end points at start *
* object/array - start/end points at the *
* object/array start/end *
* error - [OUT] error message *
* *
* Return value: SUCCEED - value was returned successfully *
* FAIL - otherwise *
* *
******************************************************************************/
static int webdriver_get_value(const char *response, struct zbx_json_parse *jp, char **error)
{
struct zbx_json_parse jp_resp;
if (SUCCEED != zbx_json_open(response, &jp_resp))
{
*error = zbx_dsprintf(NULL, "cannot open webdriver response: %s", zbx_json_strerror());
return FAIL;
}
if (SUCCEED != zbx_json_brackets_by_name(&jp_resp, "value", jp))
{
if (NULL == (jp->start = jp->end = zbx_json_pair_by_name(&jp_resp, "value")))
{
*error = zbx_dsprintf(NULL, "cannot parse webdriver response: %s", zbx_json_strerror());
return FAIL;
}
}
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: free webdriver error object *
* *
******************************************************************************/
static void webdriver_free_error(zbx_wd_error_t *err)
{
zbx_free(err->error);
zbx_free(err->message);
zbx_free(err);
}
/******************************************************************************
* *
* Purpose: create webdriver error object *
* *
* Return value: webdriver error object *
* *
******************************************************************************/
static zbx_wd_error_t *webdriver_create_error(int http_code, const char *error, const char *message)
{
zbx_wd_error_t *err;
err = (zbx_wd_error_t *)zbx_malloc(NULL, sizeof(zbx_wd_error_t));
err->http_code = http_code;
err->error = zbx_strdup(NULL, error);
err->message = zbx_strdup(NULL, message);
return err;
}
/******************************************************************************
* *
* Purpose: discard webdriver errors *
* *
******************************************************************************/
void webdriver_discard_error(zbx_webdriver_t *wd)
{
if (NULL != wd->error)
{
webdriver_free_error(wd->error);
wd->error = NULL;
}
zbx_free(wd->last_error_message);
}
/******************************************************************************
* *
* Purpose: get error returned by webdriver *
* {"error":, "message":} *
* *
* Parameters: wd - [IN] webdriver object *
* jp - [IN] webdriver response *
* info - [OUT] error message *
* *
* Return value: SUCCEED - error was found *
* FAIL - otherwise *
* *
* Comments: If error was found in response the webdriver error object will *
* be created from curl http status code, error and message tag *
* contents. The message will be returned as error message in info *
* parameter. *
* *
******************************************************************************/
static int webdriver_get_error(zbx_webdriver_t *wd, const struct zbx_json_parse *jp, char **info)
{
size_t error_alloc = 0, info_alloc = 0;
char *error = NULL;
long http_code;
if (ZBX_JSON_TYPE_OBJECT != zbx_json_valuetype(jp->start))
return FAIL;
if (SUCCEED != zbx_json_value_by_name_dyn(jp, "error", &error, &error_alloc, NULL))
return FAIL;
/* return more informative message as error text */
if (SUCCEED != zbx_json_value_by_name_dyn(jp, "message", info, &info_alloc, NULL))
*info = zbx_strdup(NULL, "");
if (CURLE_OK != curl_easy_getinfo(wd->handle, CURLINFO_RESPONSE_CODE, &http_code))
http_code = 0;
webdriver_discard_error(wd);
wd->error = webdriver_create_error((int)http_code, error, *info);
zbx_free(error);
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: performs webdriver query *
* *
* Parameters: wd - [IN] webdriver object *
* method - [IN] HTTP method *
* command - [IN] webdriver command (optional, can be NULL) *
* data - [IN] data to post (optional, can be NULL) *
* jp - [OUT] returned value (optional, can be NULL) *
* error - [OUT] error message *
* *
* Return value: SUCCEED - query was performed successfully *
* FAIL - otherwise *
* *
* Comments: The webdriver response is inspected for error value and if found *
* then error is returned. *
* *
******************************************************************************/
static int webdriver_session_query(zbx_webdriver_t *wd, const char *method, const char *command, const char *data,
struct zbx_json_parse *jp, char **error)
{
char *url = NULL;
size_t url_alloc = 0, url_offset = 0;
CURLcode err;
int ret = FAIL;
struct zbx_json_parse jp_value;
webdriver_discard_error(wd);
zbx_rtrim(wd->endpoint, "/");
zbx_snprintf_alloc(&url, &url_alloc, &url_offset, "%s/session", wd->endpoint);
if (NULL != wd->session)
{
zbx_chrcpy_alloc(&url, &url_alloc, &url_offset, '/');
zbx_strcpy_alloc(&url, &url_alloc, &url_offset, wd->session);
}
if (NULL != command)
{
if (NULL == wd->session && 0 != strcmp(method, "POST"))
{
*error = zbx_strdup(NULL, "webdriver session has not been opened");
goto out;
}
zbx_chrcpy_alloc(&url, &url_alloc, &url_offset, '/');
zbx_strcpy_alloc(&url, &url_alloc, &url_offset, command);
}
if (SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_URL, url, error) ||
SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_CUSTOMREQUEST, method, error))
{
goto out;
}
zabbix_log(LOG_LEVEL_TRACE, "webdriver %s url:%s data:%s", method, url, ZBX_NULL2EMPTY_STR(data));
if (0 == strcmp(method, "POST"))
{
if (SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_POSTFIELDS, ZBX_NULL2EMPTY_STR(data), error))
goto out;
}
else
{
if (SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_POST, 0L, error))
goto out;
}
if (0 != wd->data_alloc)
{
wd->data_offset = 0;
*wd->data = '\0';
}
if (CURLE_OK != (err = curl_easy_perform(wd->handle)))
{
*error = zbx_dsprintf(NULL, "cannot perform request %s session/%s: %s",
method, ZBX_NULL2EMPTY_STR(command), curl_easy_strerror(err));
goto out;
}
if (0 == wd->data_offset)
{
*error = zbx_dsprintf(NULL, "cannot perform request %s session/%s: received empty response",
method, ZBX_NULL2EMPTY_STR(command));
goto out;
}
zabbix_log(LOG_LEVEL_TRACE, "webdriver response: %s", wd->data);
if (NULL == jp)
jp = &jp_value;
if (SUCCEED != webdriver_get_value(wd->data, jp, error) || SUCCEED == webdriver_get_error(wd, jp, error))
goto out;
ret = SUCCEED;
out:
zbx_free(url);
return ret;
}
/******************************************************************************
* *
* Purpose: close webdriver session *
* *
* Parameters: wd - [IN] webdriver object *
* *
******************************************************************************/
static void webdriver_close_session(zbx_webdriver_t *wd)
{
char *error = NULL;
if (SUCCEED != webdriver_session_query(wd, "DELETE", NULL, NULL, NULL, &error))
{
zabbix_log(LOG_LEVEL_DEBUG, "cannot close webdriver session: %s", error);
zbx_free(error);
}
else
zabbix_log(LOG_LEVEL_DEBUG, "closed webdriver session %s", wd->session);
}
/******************************************************************************
* *
* Purpose: create webdriver object *
* *
* Parameters: browser - [IN] browser type *
* endpoint - [IN] webdriver URL *
* sourceip - [IN] source ip (optional, can be NULL) *
* error - [OUT] error message *
* *
* Return value: created webdriver object or NULL in the case of error *
* *
******************************************************************************/
zbx_webdriver_t *webdriver_create(const char *endpoint, const char *sourceip, char **error)
{
int ret = FAIL;
zbx_webdriver_t *wd;
wd = (zbx_webdriver_t *)zbx_malloc(NULL, sizeof(zbx_webdriver_t));
memset(wd, 0, sizeof(zbx_webdriver_t));
wd->endpoint = zbx_strdup(NULL, endpoint);
if (NULL == (wd->handle = curl_easy_init()))
{
*error = zbx_dsprintf(NULL, "cannot initialize cURL library");
goto out;
}
wd->headers = curl_slist_append(wd->headers, "Content-type: application/json; charset=utf-8");
if (SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_HTTPHEADER, wd->headers, error) ||
SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_COOKIEFILE, "", error) ||
SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_FOLLOWLOCATION, 1L, error) ||
SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_WRITEFUNCTION, curl_write_cb, error) ||
SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_WRITEDATA, wd, error) ||
SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_SSL_VERIFYPEER, 0L, error) ||
SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_SSL_VERIFYHOST, 0L, error) ||
SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_HEADERFUNCTION, curl_header_cb, error) ||
SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_HEADERDATA, wd, error) ||
(NULL != sourceip && SUCCEED != CURL_SETOPT(wd->handle, CURLOPT_INTERFACE, sourceip, error)))
{
goto out;
}
if (SUCCEED != zbx_curl_setopt_https(wd->handle, error))
goto out;
wd->refcount = 1;
wd_perf_init(&wd->perf);
ret = SUCCEED;
out:
if (SUCCEED != ret)
{
webdriver_destroy(wd);
wd = NULL;
}
return wd;
}
/******************************************************************************
* *
* Purpose: destroy webdriver object *
* *
* Parameters: wd - [IN] webdriver object *
* *
******************************************************************************/
void webdriver_destroy(zbx_webdriver_t *wd)
{
zabbix_log(LOG_LEVEL_DEBUG, "webdriver_destroy()");
wd_perf_destroy(&wd->perf);
if (NULL != wd->session)
{
webdriver_close_session(wd);
zbx_free(wd->session);
}
if (NULL != wd->handle)
curl_easy_cleanup(wd->handle);
if (NULL != wd->headers)
curl_slist_free_all(wd->headers);
zbx_free(wd->endpoint);
zbx_free(wd->data);
zbx_free(wd->headers_in);
zbx_free(wd->last_error_message);
if (NULL != wd->error)
webdriver_free_error(wd->error);
zbx_free(wd);
}
/******************************************************************************
* *
* Purpose: release webdriver object (decrement reference count and destroy *
* if necessary) *
* *
* Parameters: wd - [IN] webdriver object *
* *
******************************************************************************/
void webdriver_release(zbx_webdriver_t *wd)
{
zabbix_log(LOG_LEVEL_DEBUG, "webdriver_release()");
if (0 == --wd->refcount)
webdriver_destroy(wd);
}
/******************************************************************************
* *
* Purpose: increment webdriver object reference count *
* *
* Parameters: wd - [IN] webdriver object *
* *
******************************************************************************/
zbx_webdriver_t *webdriver_addref(zbx_webdriver_t *wd)
{
wd->refcount++;
return wd;
}
/******************************************************************************
* *
* Purpose: open webdriver session *
* *
* Parameters: wd - [IN] webdriver object *
* capabilities - [IN] browser capabilities (JSON formatted) *
* error - [OUT] error message *
* *
* Return value: SUCCEED - session was opened successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_open_session(zbx_webdriver_t *wd, const char *capabilities, char **error)
{
#define WEBDRIVER_DEFAULT_SCREEN_WIDTH 1920
#define WEBDRIVER_DEFAULT_SCREEN_HEIGHT 1080
int ret = FAIL;
struct zbx_json_parse jp;
size_t session_alloc = 0;
if (SUCCEED != webdriver_session_query(wd, "POST", NULL, capabilities, &jp, error))
goto out;
if (SUCCEED != zbx_json_value_by_name_dyn(&jp, "sessionId", &wd->session, &session_alloc, NULL))
{
*error = zbx_dsprintf(NULL, "cannot read sessionId: %s", zbx_json_strerror());
goto out;
}
if (SUCCEED != webdriver_set_screen_size(wd, WEBDRIVER_DEFAULT_SCREEN_WIDTH, WEBDRIVER_DEFAULT_SCREEN_HEIGHT,
error))
{
goto out;
}
zabbix_log(LOG_LEVEL_DEBUG, "opened webdriver session with sessionid:%s", wd->session);
wd->create_time = zbx_time();
ret = SUCCEED;
out:
return ret;
#undef WEBDRIVER_DEFAULT_SCREEN_WIDTH
#undef WEBDRIVER_DEFAULT_SCREEN_HEIGHT
}
/******************************************************************************
* *
* Purpose: navigate to url *
* *
* Parameters: wd - [IN] webdriver object *
* url - [IN] target url *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_url(zbx_webdriver_t *wd, const char *url, char **error)
{
struct zbx_json json;
int ret;
zbx_json_init(&json, 128);
zbx_json_addstring(&json, "url", url, ZBX_JSON_TYPE_STRING);
ret = webdriver_session_query(wd, "POST", "url", json.buffer, NULL, error);
zbx_json_free(&json);
return ret;
}
/******************************************************************************
* *
* Purpose: get current url *
* *
* Parameters: wd - [IN] webdriver object *
* url - [OUT] current url *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_get_url(zbx_webdriver_t *wd, char **url, char **error)
{
struct zbx_json_parse jp;
size_t url_alloc = 0;
if (SUCCEED != webdriver_session_query(wd, "GET", "url", NULL, &jp, error))
return FAIL;
if (NULL == zbx_json_decodevalue_dyn(jp.start, url, &url_alloc, NULL))
{
*error = zbx_dsprintf(NULL, "cannot read url: %s", zbx_json_strerror());
return FAIL;
}
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: find single element *
* *
* Parameters: wd - [IN] webdriver object *
* strategy - [IN] xpath, css selector, ... (see webdriver doc) *
* selector - [IN] (see webdriver doc) *
* element - [OUT] element id *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_find_element(zbx_webdriver_t *wd, const char *strategy, const char *selector, char **element,
char **error)
{
struct zbx_json json;
int ret = FAIL;
struct zbx_json_parse jp;
size_t element_alloc = 0;
char buffer[MAX_STRING_LEN];
const char *value;
zbx_json_init(&json, 128);
zbx_json_addstring(&json, "using", strategy, ZBX_JSON_TYPE_STRING);
zbx_json_addstring(&json, "value", selector, ZBX_JSON_TYPE_STRING);
if (SUCCEED != webdriver_session_query(wd, "POST", "element", json.buffer, &jp, error))
{
/* throw exception in the case of connection errors */
if (NULL == wd->error || 404 != wd->error->http_code ||
0 == strcmp(wd->error->error, WEBDRIVER_INVALID_SESSIONID_ERROR))
goto out;
/* otherwise log the error and return NULL element */
zabbix_log(LOG_LEVEL_DEBUG, "cannot find element with strategy:'%s' and selector:'%s': %s",
strategy, selector, *error);
webdriver_discard_error(wd);
zbx_free(*error);
*element = NULL;
}
else if (NULL == (value = zbx_json_pair_next(&jp, NULL, buffer, sizeof(buffer))) ||
NULL == zbx_json_decodevalue_dyn(value, element, &element_alloc, NULL))
{
*element = NULL;
zabbix_log(LOG_LEVEL_DEBUG, "cannot read element: %s", zbx_json_strerror());
goto out;
}
ret = SUCCEED;
out:
zbx_json_free(&json);
return ret;
}
/******************************************************************************
* *
* Purpose: find multiple elements *
* *
* Parameters: wd - [IN] webdriver object *
* strategy - [IN] xpath, css selector, ... (see webdriver doc) *
* selector - [IN] (see webdriver doc) *
* elements - [OUT] vector of element identifiers *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_find_elements(zbx_webdriver_t *wd, const char *strategy, const char *selector,
zbx_vector_str_t *elements, char **error)
{
struct zbx_json json;
int ret = FAIL;
struct zbx_json_parse jp;
char buffer[MAX_STRING_LEN];
zbx_json_init(&json, 128);
zbx_json_addstring(&json, "using", strategy, ZBX_JSON_TYPE_STRING);
zbx_json_addstring(&json, "value", selector, ZBX_JSON_TYPE_STRING);
if (SUCCEED != webdriver_session_query(wd, "POST", "elements", json.buffer, &jp, error))
{
/* otherwise log the error and return NULL element */
zabbix_log(LOG_LEVEL_DEBUG, "cannot find element with strategy:'%s' and selector:'%s': %s",
strategy, selector, *error);
goto out;
}
else
{
struct zbx_json_parse jp_elements;
if (SUCCEED == zbx_json_brackets_open(jp.start, &jp_elements))
{
const char *p = NULL;
while (NULL != (p = zbx_json_next(&jp_elements, p)))
{
struct zbx_json_parse jp_element;
const char *value;
char *element = NULL;
size_t element_alloc = 0;
if (SUCCEED != zbx_json_brackets_open(p, &jp_element))
continue;
if (NULL == (value = zbx_json_pair_next(&jp_element, NULL, buffer, sizeof(buffer))))
continue;
if (NULL != zbx_json_decodevalue_dyn(value, &element, &element_alloc, NULL))
zbx_vector_str_append(elements, element);
}
}
}
ret = SUCCEED;
out:
zbx_json_free(&json);
return ret;
}
/******************************************************************************
* *
* Purpose: send keys to element *
* *
* Parameters: wd - [IN] webdriver object *
* element - [IN] target element identifier *
* keys - [IN] keys to send *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_send_keys_to_element(zbx_webdriver_t *wd, const char *element, const char *keys, char **error)
{
struct zbx_json json;
int ret = FAIL;
char *command = NULL;
zbx_json_init(&json, 128);
zbx_json_addstring(&json, "text", keys, ZBX_JSON_TYPE_STRING);
command = zbx_dsprintf(NULL, "element/%s/value", element);
ret = webdriver_session_query(wd, "POST", command, json.buffer, NULL, error);
zbx_free(command);
zbx_json_free(&json);
return ret;
}
/******************************************************************************
* *
* Purpose: click element *
* *
* Parameters: wd - [IN] webdriver object *
* element - [IN] target element identifier *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_click_element(zbx_webdriver_t *wd, const char *element, char **error)
{
struct zbx_json json;
int ret = FAIL;
char *command = NULL;
zbx_json_init(&json, 128);
zbx_json_addstring(&json, "id", element, ZBX_JSON_TYPE_STRING);
command = zbx_dsprintf(NULL, "element/%s/click", element);
ret = webdriver_session_query(wd, "POST", command, json.buffer, NULL, error);
zbx_free(command);
zbx_json_free(&json);
return ret;
}
/******************************************************************************
* *
* Purpose: clear element *
* *
* Parameters: wd - [IN] webdriver object *
* element - [IN] target element identifier *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_clear_element(zbx_webdriver_t *wd, const char *element, char **error)
{
struct zbx_json json;
int ret = FAIL;
char *command = NULL;
zbx_json_init(&json, 128);
zbx_json_addstring(&json, "id", element, ZBX_JSON_TYPE_STRING);
command = zbx_dsprintf(NULL, "element/%s/clear", element);
ret = webdriver_session_query(wd, "POST", command, json.buffer, NULL, error);
zbx_free(command);
zbx_json_free(&json);
return ret;
}
/******************************************************************************
* *
* Purpose: get element information (attributes, properties, text) *
* *
* Parameters: wd - [IN] webdriver object *
* element - [IN] target element identifier *
* info - [IN] information to get (attribute, property, text) *
* name - [IN] attribute/property name, NULL for text *
* value - [OUT] attribute/property/text value *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_get_element_info(zbx_webdriver_t *wd, const char *element, const char *info, const char *name,
char **value, char **error)
{
int ret = FAIL;
char *command = NULL;
size_t command_alloc = 0, command_offset = 0, value_alloc = 0;
struct zbx_json_parse jp;
zbx_snprintf_alloc(&command, &command_alloc, &command_offset, "element/%s/%s", element, info);
if (NULL != name)
zbx_snprintf_alloc(&command, &command_alloc, &command_offset, "/%s", name);
if (SUCCEED != webdriver_session_query(wd, "GET", command, NULL, &jp, error))
goto out;
if (NULL == zbx_json_decodevalue_dyn(jp.start, value, &value_alloc, NULL))
{
*error = zbx_dsprintf(NULL, "cannot parse %s value: %s", info, zbx_json_strerror());
goto out;
}
ret = SUCCEED;
out:
zbx_free(command);
return ret;
}
/******************************************************************************
* *
* Purpose: set webdriver timeouts *
* *
* Parameters: wd - [IN] webdriver object *
* script_timeout - [IN] script timeout (ms) or -1 *
* page_load_timeout - [IN] page load timeout (ms) or -1 *
* implicit_timeout - [IN] implicit timeout (ms) or -1 *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
* Comments: When timeout parameter is set to -1 the timeout value is not *
* changed. *
* *
******************************************************************************/
int webdriver_set_timeouts(zbx_webdriver_t *wd, int script_timeout, int page_load_timeout, int implicit_timeout,
char **error)
{
struct zbx_json json;
int ret = FAIL;
zbx_json_init(&json, 128);
if (-1 != script_timeout)
zbx_json_addint64(&json, "script", script_timeout);
if (-1 != page_load_timeout)
zbx_json_addint64(&json, "pageLoad", page_load_timeout);
if (-1 != implicit_timeout)
zbx_json_addint64(&json, "implicit", implicit_timeout);
ret = webdriver_session_query(wd, "POST", "timeouts", json.buffer, NULL, error);
zbx_json_free(&json);
return ret;
}
/******************************************************************************
* *
* Purpose: get cookies *
* *
* Parameters: wd - [IN] webdriver object *
* cookies - [OUT] array of cookie objects in JSON format *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_get_cookies(zbx_webdriver_t *wd, char **cookies, char **error)
{
struct zbx_json_parse jp;
if (SUCCEED != webdriver_session_query(wd, "GET", "cookie", NULL, &jp, error))
return FAIL;
size_t len = (size_t)(jp.end - jp.start + 1);
*cookies = zbx_malloc(NULL, len + 1);
memcpy(*cookies, jp.start, len);
(*cookies)[len] = '\0';
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: add cookie *
* *
* Parameters: wd - [IN] webdriver object *
* cookie - [IN] cookie object in JSON format *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_add_cookie(zbx_webdriver_t *wd, const char *cookie, char **error)
{
struct zbx_json json;
int ret = FAIL;
zbx_json_init(&json, 128);
zbx_json_addraw(&json, "cookie", cookie);
ret = webdriver_session_query(wd, "POST", "cookie", json.buffer, NULL, error);
zbx_json_free(&json);
return ret;
}
/******************************************************************************
* *
* Purpose: capture screen *
* *
* Parameters: wd - [IN] webdriver object *
* screenshot - [OUT] base64 encoded captured screenshot *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_get_screenshot(zbx_webdriver_t *wd, char **screenshot, char **error)
{
struct zbx_json_parse jp;
size_t screenshot_alloc = 0;
if (SUCCEED != webdriver_session_query(wd, "GET", "screenshot", NULL, &jp, error))
return FAIL;
if (NULL == zbx_json_decodevalue_dyn(jp.start, screenshot, &screenshot_alloc, NULL))
{
*error = zbx_dsprintf(NULL, "cannot extract screenshot from json: %s", zbx_json_strerror());
return FAIL;
}
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: set screen size *
* *
* Parameters: wd - [IN] webdriver object *
* width - [IN] screen width *
* height - [IN] screen height *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_set_screen_size(zbx_webdriver_t *wd, int width, int height, char **error)
{
struct zbx_json json;
int ret = FAIL;
zbx_json_init(&json, 128);
zbx_json_addint64(&json, "width", width);
zbx_json_addint64(&json, "height", height);
ret = webdriver_session_query(wd, "POST", "window/rect", json.buffer, NULL, error);
zbx_json_free(&json);
return ret;
}
/******************************************************************************
* *
* Purpose: execute script on webdriver *
* *
* Parameters: wd - [IN] webdriver object *
* script - [IN] script to execute *
* jp - [OUT] script execution result *
* error - [OUT] error message *
* *
* Return value: SUCCEED - query was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_execute_script(zbx_webdriver_t *wd, const char *script, struct zbx_json_parse *jp,
char **error)
{
struct zbx_json json;
int ret = FAIL;
zbx_json_init(&json, 128);
zbx_json_addstring(&json, "script", script, ZBX_JSON_TYPE_STRING);
zbx_json_addarray(&json, "args");
zbx_json_close(&json);
if (SUCCEED != webdriver_session_query(wd, "POST", "execute/sync", json.buffer, jp, error))
goto out;
ret = SUCCEED;
out:
zbx_json_free(&json);
return ret;
}
/******************************************************************************
* *
* Purpose: get performance entries from performance object *
* *
* Parameters: wd - [IN] webdriver object *
* jp - [OUT] performance entries *
* (optional, can be null) *
* error - [OUT] error message *
* *
* Return value: SUCCEED - query was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_get_perf_data(zbx_webdriver_t *wd, struct zbx_json_parse *jp, char **error)
{
const char *script =
"var a=window.performance.getEntries();var out=[];"
"for (o of a) {"
"var obj = {};"
"for (p in o) {"
"if (!(o[p] instanceof Object) && typeof o[p] !== 'function') {obj[p] = o[p];}"
"}"
"out.push(obj);"
"}; return out;";
return webdriver_execute_script(wd, script, jp, error);
}
/******************************************************************************
* *
* Purpose: get raw performance entries from performance object *
* *
* Parameters: wd - [IN] webdriver object *
* type - [IN] entry type *
* jp - [OUT] performance entries *
* (optional, can be null) *
* error - [OUT] error message *
* *
* Return value: SUCCEED - query was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_get_raw_perf_data(zbx_webdriver_t *wd, const char *type, struct zbx_json_parse *jp, char **error)
{
char *script;
int ret;
if (NULL != type)
{
if (NULL != strchr(type, '\'') || NULL != strchr(type, '\\'))
{
*error = zbx_strdup(NULL, "invalid performance entry type");
return FAIL;
}
script = zbx_dsprintf(NULL, "return window.performance.getEntriesByType('%s')", type);
}
else
script = zbx_strdup(NULL, "return window.performance.getEntries();");
ret = webdriver_execute_script(wd, script, jp, error);
zbx_free(script);
return ret;
}
/******************************************************************************
* *
* Purpose: get performance entries from performance object *
* *
* Parameters: wd - [IN] webdriver object *
* bookmark - [IN] performance entry bookmark *
* (optional, can be null) *
* error - [OUT] error message *
* *
* Return value: SUCCEED - query was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_collect_perf_data(zbx_webdriver_t *wd, const char *bookmark, char **error)
{
struct zbx_json_parse jp;
int ret = FAIL;
if (SUCCEED != webdriver_get_perf_data(wd, &jp, error))
goto out;
ret = wd_perf_collect(&wd->perf, bookmark, &jp, error);
out:
return ret;
}
/******************************************************************************
* *
* Purpose: get page source *
* *
* Parameters: wd - [IN] webdriver object *
* source - [OUT] page source *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_get_page_source(zbx_webdriver_t *wd, char **source, char **error)
{
struct zbx_json_parse jp;
size_t source_alloc = 0;
if (SUCCEED != webdriver_session_query(wd, "GET", "source", NULL, &jp, error))
return FAIL;
if (NULL == zbx_json_decodevalue_dyn(jp.start, source, &source_alloc, NULL))
{
*error = zbx_dsprintf(NULL, "cannot extract page source from json: %s", zbx_json_strerror());
return FAIL;
}
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: check if webdriver has cached an error *
* *
* Parameters: wd - [IN] webdriver object *
* *
* Return value: SUCCEED - webdriver has cached an error *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_has_error(zbx_webdriver_t *wd)
{
return NULL == wd->last_error_message ? FAIL : SUCCEED;
}
/******************************************************************************
* *
* Purpose: set custom error message *
* *
* Parameters: wd - [IN] webdriver object *
* message - [IN] error message *
* *
* Comments: The error message must be preallocated by caller and will be *
* freed when webdriver custom error message is freed. *
* *
******************************************************************************/
void webdriver_set_error(zbx_webdriver_t *wd, char *message)
{
webdriver_discard_error(wd);
wd->last_error_message = message;
}
/******************************************************************************
* *
* Purpose: get alert text *
* *
* Parameters: wd - [IN] webdriver object *
* text - [OUT] alert text or null if there are no alerts *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int webdriver_get_alert(zbx_webdriver_t *wd, char **text, char **error)
{
int ret = FAIL;
struct zbx_json_parse jp;
size_t text_alloc = 0;
if (SUCCEED != webdriver_session_query(wd, "GET", "alert/text", NULL, &jp, error))
{
/* throw exception in the case of connection errors */
if (NULL == wd->error || 404 != wd->error->http_code ||
0 == strcmp(wd->error->error, WEBDRIVER_INVALID_SESSIONID_ERROR))
{
goto out;
}
/* otherwise log the error and return NULL alert */
zabbix_log(LOG_LEVEL_DEBUG, "cannot get alert text: %s", error);
webdriver_discard_error(wd);
zbx_free(*error);
*text = NULL;
}
else if (NULL == zbx_json_decodevalue_dyn(jp.start, text, &text_alloc, NULL))
{
*text = NULL;
zabbix_log(LOG_LEVEL_DEBUG, "cannot read alert text: %s", zbx_json_strerror());
goto out;
}
ret = SUCCEED;
out:
return ret;
}
int webdriver_accept_alert(zbx_webdriver_t *wd, char **error)
{
return webdriver_session_query(wd, "POST", "alert/accept", "{}", NULL, error);
}
int webdriver_dismiss_alert(zbx_webdriver_t *wd, char **error)
{
return webdriver_session_query(wd, "POST", "alert/dismiss", "{}", NULL, error);
}
/******************************************************************************
* *
* Purpose: switch to frame *
* *
* Parameters: wd - [IN] webdriver object *
* frame - [IN] target frame *
* error - [OUT] error message *
* *
* Return value: SUCCEED - operation was performed successfully *
* FAIL - otherwise *
* *
* Comments: The switching depends on frame contents: *
* NULL - switch to top level browsing context *
* number - switch to the frame by index *
* otherwise - switch to the frame by element *
* *
******************************************************************************/
int webdriver_switch_frame(zbx_webdriver_t *wd, const char *frame, char **error)
{
struct zbx_json json;
int ret;
zbx_uint64_t id;
zbx_json_init(&json, 128);
if (NULL == frame)
{
zbx_json_addraw(&json, "id", "null");
}
else if (SUCCEED == zbx_is_uint64(frame, &id))
{
zbx_json_adduint64(&json, "id", id);
}
else
{
zbx_json_addobject(&json, "id");
zbx_json_addstring(&json, WEBDRIVER_ELEMENT_ID, frame, ZBX_JSON_TYPE_STRING);
}
ret = webdriver_session_query(wd, "POST", "frame", json.buffer, NULL, error);
zbx_json_free(&json);
return ret;
}
#endif