Skip to content

Commit b075dce

Browse files
author
Jani Mikkonen
committed
Get Log for firefox and firefox profile kw
Added support for Get Log keyword to return browser type logs for Firefox. In order for get_log() to return anything meaningful, firefox profile needs to have right properties. These can be generated by Generate Firefox Profile.
1 parent 936d5ce commit b075dce

File tree

7 files changed

+129
-20
lines changed

7 files changed

+129
-20
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,14 @@ tests do show they work.
163163

164164
* Sets arbituary attribute of the element into provided value
165165

166+
* Get Log **log_type**
167+
168+
* Returns log_type logs from current browser. If Browser is firefox and profile generated with Generate Firefox Profile keyword is used, Get Log is also able to return browser type logs for it.
169+
170+
* Generate Firefox Profile **options** **accept_untrusted_certs** **proxy**
171+
172+
* Generates firefox with given options. Also can disable ssl cert checking and setting the proxy.
173+
166174
# Keyword Documentation
167175

168176
Keyword documentation [here](https://salabs.github.io/robotframework-seleniumtestability/index.html) and if you need to create one for offline usage:

assets/templates/code.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,13 @@ var redirectTrigger = function() {
8989
var redirurl = "https://httpbin.org/redirect-to?url=" + cur + "&status_code=302"
9090
window.location = redirurl
9191
}
92+
93+
94+
var logTrigger = function() {
95+
console.log("LOGTRIGGER: log")
96+
console.error("LOGTRIGGER: error")
97+
console.debug("LOGTRIGGER: debug")
98+
console.trace("LOGTRIGGER: trace")
99+
console.info("LOGTRIGGER: info")
100+
logTriggerError()
101+
}

assets/templates/index.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,12 @@
7070
<body>
7171
<table border="1">
7272
<tr>
73-
<td>
73+
<td>
7474
<button id="redirect-button" type="button" name="redirect" onClick="redirectTrigger()">Redirect loop</button>
75-
</td>
75+
</td>
76+
<td>
77+
<button id="log-button" type="button" name="log" onClick="logTrigger()">Log Something</button>
78+
</td>
7679
</tr>
7780
<tr>
7881
<td>

atest/keywords.robot

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ Setup Web Environment
1717
[Arguments] ${BROWSER} ${URL}
1818
[Documentation] Opens a browser with given url
1919
${URL}= Set Variable ${URL}
20+
${FF_PROFILE}= Generate Firefox Profile
2021
Set Selenium Timeout 10 seconds
2122
Set Selenium Speed 0 seconds
22-
Open Browser ${URL} browser=${BROWSER}
23+
Open Browser ${URL} browser=${BROWSER} ff_profile_dir=${FF_PROFILE.path}
2324
Wait For Document Ready
2425

2526
Start Flask App

atest/logs.robot

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,42 @@
22
Documentation Verifies log fetching
33
Suite Setup Start Flask App
44
Suite Teardown Stop Flask App
5-
Test Teardown Teardown Web Environment
65
Test Template Test Get Log
6+
Test Teardown Teardown Web Environment
77
Library SeleniumLibrary plugins=${CURDIR}/../src/SeleniumTestability;True;29 seconds;False
88
Resource keywords.robot
99
Library Collections
1010

1111
*** Test Cases ***
1212
Logs With Firefox
13-
${FF} loggingPrefs 0
13+
${FF} loggingPrefs 2 7 11
1414

1515
Logs With Chrome
16-
${GC} goog:loggingPrefs 10
16+
${GC} goog:loggingPrefs 2 5 10
1717

1818
*** Keywords ***
1919
Local Setup Test Environment
20-
[Arguments] ${BROWSER} ${PREFS} ${ROWS}
20+
[Arguments] ${BROWSER} ${PREFS}
2121
[Documentation] normal test setup but with desired_capabilities added
2222
${cap}= Generate Capabilities with Logging Prefs ${BROWSER} ${PREFS}
23-
Open Browser ${URL} browser=${BROWSER} desired_capabilities=${cap}
23+
${FF_PROFILE}= Generate Firefox Profile
24+
Open Browser ${URL} browser=${BROWSER} desired_capabilities=${cap} ff_profile_dir=${FF_PROFILE.path}
2425
Wait For Document Ready
25-
FOR ${idx} IN RANGE ${ROWS}
26-
Execute Javascript console.log("Hello World ${idx}")
27-
END
2826

2927
Test Get Log
30-
[Arguments] ${BROWSER} ${PREFS} ${ROWS}
28+
[Arguments] ${BROWSER} ${PREFS} ${ROWS} ${FIRST} ${SECOND}
3129
[Documentation] Verifies get_log returns something when possible and doesnt cause errors
32-
Local Setup Test Environment ${BROWSER} ${PREFS} ${ROWS}
30+
Local Setup Test Environment ${BROWSER} ${PREFS}
31+
Click Button id:log-button
32+
${LOG}= Get Log browser
33+
Length Should Be ${LOG} ${FIRST}
34+
FOR ${idx} IN RANGE ${ROWS}
35+
Execute Javascript console.log("Hello World ${idx}")
36+
Execute Javascript console.info("Hello World ${idx} as info")
37+
Execute Javascript console.warn("Hello World ${idx} as warn")
38+
Execute Javascript console.error("Hello World ${idx} as error")
39+
Execute Javascript console.trace("Hello World ${idx} as trace")
40+
Execute Javascript console.debug("Hello World ${idx} as debug")
41+
END
3342
${LOG}= Get Log browser
34-
Length Should Be ${LOG} ${ROWS}
43+
Length Should Be ${LOG} ${SECOND}

src/SeleniumTestability/plugin.py

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@
66
from .listener import TestabilityListener
77
from .javascript import JS_LOOKUP
88
from .logger import get_logger, argstr, kwargstr
9-
from .types import WebElementType, LocatorType, OptionalBoolType, OptionalStrType, BrowserLogsType, OptionalDictType
9+
from .types import WebElementType, LocatorType, OptionalBoolType, OptionalStrType, BrowserLogsType, OptionalDictType, is_firefox
1010
from robot.utils import is_truthy, timestr_to_secs, secs_to_timestr
1111
from selenium.webdriver.support.ui import WebDriverWait
1212
from selenium.common.exceptions import TimeoutException, WebDriverException
1313
from http.cookies import SimpleCookie
1414
from furl import furl
1515
from typing import Dict, Callable, Any
1616
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
17+
from selenium.webdriver import FirefoxProfile
1718
import wrapt
19+
import re
20+
import json
21+
from time import time
1822

1923

2024
@wrapt.decorator
@@ -171,6 +175,7 @@ def __init__(
171175
self.hidden_elements = {} # type: Dict[str, str]
172176
self.browser_warn_shown = False
173177
self.empty_log_warn_shown = False
178+
self.ff_log_pos = {} # type: Dict[str, int]
174179

175180
@log_wrapper
176181
def _inject_testability(self: "SeleniumTestability") -> None:
@@ -502,19 +507,58 @@ def element_should_not_be_blocked(self: "SeleniumTestability", locator: LocatorT
502507
if is_blocked:
503508
raise AssertionError("Element with locator {} is blocked".format(locator))
504509

510+
def _get_ff_log(self: "SeleniumTestability", name: str) -> BrowserLogsType:
511+
matcher = r"^(?P<source>JavaScript|console)(\s|\.)(?P<level>warn.*?|debug|trace|log|error|info):\s(?P<message>.*)$"
512+
LEVEL_LOOKUP = {
513+
"log": "INFO",
514+
"warn": "WARN",
515+
"warning": "WARN",
516+
"error": "SEVERE",
517+
"info": "INFO",
518+
"trace": "SEVERE",
519+
"debug": "DEBUG",
520+
}
521+
SOURCE_LOOKUP = {"JavaScript": "javascript", "console": "console-api"}
522+
log = []
523+
skip_lines = self.ff_log_pos.get(name, 0)
524+
buff = []
525+
with open(name, "r") as f:
526+
buff = f.read().split("\n")
527+
self.ff_log_pos[name] = skip_lines + len(buff)
528+
buff = buff[skip_lines:]
529+
530+
for line in buff:
531+
matches = re.search(matcher, line)
532+
if matches:
533+
row = {
534+
"level": LEVEL_LOOKUP[matches.group("level")],
535+
"message": matches.group("message"),
536+
"source": SOURCE_LOOKUP[matches.group("source")],
537+
"timestamp": int(time() * 1000),
538+
}
539+
log.append(json.dumps(row))
540+
return log
541+
505542
@log_wrapper
506543
@keyword
507544
def get_log(self: "SeleniumTestability", log_type: str = "browser") -> BrowserLogsType:
508545
"""
509-
Returns logs determined by ``log_type`` from the current browser.
546+
Returns logs determined by ``log_type`` from the current browser. What is returned
547+
depends on desired_capabilities passed to `Open Browser`.
548+
549+
Note: On firefox, the firefox profile has to have `devtools.console.stdout.content` property to be set.
550+
This can be done automatically with `Generate Firefox Profile` and then pass that to `Open Browser`.
510551
"""
511552
ret = [] # type: BrowserLogsType
512553
try:
513-
ret = self.ctx.driver.get_log(log_type)
554+
if is_firefox(self.ctx.driver) and log_type == "browser":
555+
ret = self._get_ff_log(self.ctx.driver.service.log_file.name)
556+
else:
557+
ret = self.ctx.driver.get_log(log_type)
514558
except WebDriverException:
515559
if not self.browser_warn_shown:
516560
self.browser_warn_shown = True
517-
self.warn("Current browser does not support fetching logs from the browser")
561+
self.warn("Current browser does not support fetching logs from the browser with log_type: {}".format(log_type))
518562
return []
519563
if not ret and not self.empty_log_warn_shown:
520564
self.empty_log_warn_shown = True
@@ -534,7 +578,6 @@ def get_default_capabilities(self: "SeleniumTestability", browser_name: str) ->
534578
self.logger.debug(e)
535579
return None
536580

537-
538581
@log_wrapper
539582
@keyword
540583
def set_element_attribute(self: "SeleniumTestability", locator: LocatorType, attribute: str, value: str) -> None:
@@ -543,3 +586,27 @@ def set_element_attribute(self: "SeleniumTestability", locator: LocatorType, att
543586
"""
544587
from_element = self.el.find_element(locator)
545588
self.ctx.driver.execute_script(JS_LOOKUP["set_element_attribute"], from_element, attribute, value)
589+
590+
@log_wrapper
591+
@keyword
592+
def generate_firefox_profile(
593+
self: "SeleniumTestability",
594+
options: OptionalDictType = None,
595+
accept_untrusted_certs: bool = False,
596+
proxy: OptionalStrType = None,
597+
) -> FirefoxProfile:
598+
profile = FirefoxProfile()
599+
if options:
600+
for key, value in options.items(): # type: ignore
601+
profile.set_preference(key, value)
602+
603+
profile.set_preference("devtools.console.stdout.content", True)
604+
605+
if accept_untrusted_certs:
606+
profile.accept_untrusted_certs
607+
608+
if proxy:
609+
profile.set_proxy(proxy)
610+
611+
profile.update_preferences()
612+
return profile

src/SeleniumTestability/types.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from typing import Union, Optional, List
3-
from selenium.webdriver.support.event_firing_webdriver import EventFiringWebElement
3+
from selenium.webdriver.support.event_firing_webdriver import EventFiringWebElement, EventFiringWebDriver
4+
from selenium.webdriver import Firefox
45
from selenium.webdriver.remote.webelement import WebElement
56
from psutil import Process
67

@@ -11,3 +12,13 @@
1112
OptionalDictType = Optional[str]
1213
BrowserLogsType = List[str]
1314
ProcessType = Union[Process, int]
15+
FirefoxWebDriverType = Union[Firefox, EventFiringWebDriver]
16+
17+
18+
def is_firefox(webdriver: FirefoxWebDriverType) -> bool:
19+
if isinstance(webdriver, Firefox):
20+
return True
21+
if isinstance(webdriver, EventFiringWebDriver):
22+
if isinstance(webdriver.wrapped_driver, Firefox):
23+
return True
24+
return False

0 commit comments

Comments
 (0)