browserstack / github-actions

A collection of GitHub Actions for BrowserStack. For internal reference: https://browserstack.atlassian.net/wiki/spaces/ENG/pages/1750928573/GitHub+Integration+via+GitHub+Actions
MIT License
54 stars 36 forks source link

BS is not able to handle formatter logging in the github actions using BS sdk #41

Closed NurlanMirovich closed 3 months ago

NurlanMirovich commented 7 months ago

When configuring logging in pytest the github actions are throwing BS logs

BS is not able to handle the logging level -> also have pytest.ini config file

image

image

image

image

NurlanMirovich commented 7 months ago

There is still one more issue with the custom formatted log's / once the logger configurations are added there is error from the BS

image image image

image

image

NurlanMirovich commented 7 months ago

@1114204

NurlanMirovich commented 7 months ago

@nakula @raunakpilani @kalamcs @punitx Could you please take a look at it when have chance ?

NurlanMirovich commented 7 months ago

Throwing all BS logs my way image

image

karanshah-browserstack commented 7 months ago

@NurlanMirovich We are checking into the issue, and we will get back with an update soon.

NurlanMirovich commented 7 months ago

@karanshah-browserstack Thank you !

NurlanMirovich commented 7 months ago

Hi @karanshah-browserstack have this issue been fixed yet

samarsault commented 7 months ago

Hi @NurlanMirovich we're looking into the issue, please give us some time to fix this

sriteja777 commented 7 months ago

@NurlanMirovich This issue is fixed in the latest version of BrowserStack SDK 1.19.23. Can you please upgrade browserstack-sdk python package to this version and check once?

NurlanMirovich commented 7 months ago

Hi @sriteja777 @samarsault @karanshah-browserstack I have updated my requirements to

And i test in local and pipeline, but this is still not wokring, i will include my pytest logs configurations so that you can reproduce

image image image

Here is my pytest.ini file

image

Here is my log files configuration

This module contains shared fixtures, steps, and hooks.
"""
import json
import os
from pytest_bdd import given, parsers, then, when
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from data.test_data import ENROLLMENT_BASE_URL
import logging
import pytest

class ColorFormatter(logging.Formatter):
    GREEN = "\033[92m"
    LIGHT_RED = "\033[91;1m"
    RED = "\033[91m"
    YELLOW = "\033[93m"
    RESET = "\033[0m"

    FORMATS = {
        logging.DEBUG: LIGHT_RED + "%(message)s" + RESET,
        logging.INFO: GREEN + "%(message)s" + RESET,
        logging.WARNING: YELLOW + "%(levelname)s: %(message)s" + RESET,
        logging.ERROR: RED + "%(levelname)s: %(message)s" + RESET,
        logging.CRITICAL: RED + "%(levelname)s: %(message)s" + RESET,
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)

number_of_scenarios = []

@pytest.hookimpl
def pytest_bdd_before_scenario(scenario):
    number_of_scenarios.append(scenario.name)
    logging.info(f"[Scenario Name] - {scenario.name}")

@pytest.hookimpl
def pytest_bdd_after_scenario():
    logging.info(f"[Executed scenarios] - {number_of_scenarios}")

@pytest.hookimpl
def pytest_bdd_before_step(step):
    logging.info(f"[Step name] - {step.name}")

@pytest.fixture(scope="session", autouse=True)
def setup_logging():
    root_logger = logging.getLogger()
    root_logger.setLevel(logging.INFO)
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(ColorFormatter())
    root_logger.addHandler(console_handler)

@pytest.fixture
def config(scope='session'):
    # Read the file
    with open('config.json') as config_file:
        config = json.load(config_file)

    # Accept values are acceptable
    assert config['browser'] in ['firefox', 'chrome', 'headless firefox', 'headless chrome']
    assert config['platform'] in ['desktop', 'mobile', 'ios', 'android']
    assert isinstance(config['timeout'], int)
    assert config['timeout'] > 0

    # Return config so it can be used
    return config

# This fixture will run once for each test case
@pytest.fixture
def driver(config):
    global driver
    driver = None

    if config['platform'] == 'desktop':

        if config["browser"] == "chrome":
            options = webdriver.ChromeOptions()
            options.add_argument("window-position=-1000,0")
            options.set_capability('goog:loggingPrefs', {'browser': 'ALL'})
            options.add_experimental_option("detach", True)  # keep the browser open after the test run
            prefs = {
                'autofill.profile_enabled': False
            }
            options.add_experimental_option('prefs', prefs)
            if config["headless_mode"] is True:
                options.add_argument("--headless")
            driver = webdriver.Chrome(options)
            driver.maximize_window()

        elif config["browser"] == "firefox":
            options = webdriver.FirefoxOptions()
            if config["headless_mode"] is True:
                options.headless = True
            driver = webdriver.Firefox(options)

        elif config["browser"] == "edge":
            options = webdriver.EdgeOptions()
            options.use_chromium = True
            if config["headless_mode"] is True:
                options.headless = True
            driver = webdriver.Edge(options)

        elif config["browser"] == "safari":
            options = webdriver.SafariOptions()
            if config["headless_mode"] is True:
                options.headless = True
            driver = webdriver.Safari(options)

    if config['platform'] == 'mobile':

        if config["browser"] == "chrome":
            options = webdriver.ChromeOptions()
            options.add_argument("window-position=753,92")
            options.add_argument("--window-size=414,896")  # dimensions of the Iphone XR
            options.set_capability('goog:loggingPrefs', {'browser': 'ALL'})
            options.add_experimental_option("detach", True)
            prefs = {
                'autofill.profile_enabled': False
            }
            options.add_experimental_option('prefs', prefs)
            options.add_argument('--disable-notifications')
            options.add_argument("--disable-infobars")
            options.add_argument("--disable-extensions")
            if config["headless_mode"] is True:
                options.add_argument("--headless")
            driver = webdriver.Chrome(options)

        else:
            raise Exception(f'Browser "{config["browser"]}" is not supported in local mode')

    yield driver
    driver.quit()

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
    pytest_html = item.config.pluginmanager.getplugin("html")
    outcome = yield
    report = outcome.get_result()
    extra = getattr(report, "extra", [])
    if report.when == "call":
        # always add url to report
        extra.append(pytest_html.extras.url(driver.current_url))
        xfail = hasattr(report, "wasxfail")
        if (report.skipped and xfail) or (report.failed and not xfail):
            report_directory = os.path.dirname(item.config.option.htmlpath)
            file_name = report.nodeid.replace("::", "_") + ".png"
            destination_file = os.path.join(report_directory, file_name)
            driver.save_screenshot(destination_file)
            if file_name:
                html = '<div><img src="%s" alt="screenshot" style="width:300px;height=200px"' \
                       'onclick="window.open(this.src)" align="right"/></div>' % file_name
                # only add additional html on failure
                extra.append(pytest_html.extras.html(html))
        report.extra = extra

@pytest.fixture
def credentials():
    with open('credentials.json') as config_file:
        return json.load(config_file)

@given(parsers.parse('the {end_point} page is displayed'), target_fixture="driver")
def navigate_to_page(driver, end_point):
    driver.get(ENROLLMENT_BASE_URL + end_point)
    wait = WebDriverWait(driver, 30)
    wait.until(expected_conditions.url_contains(end_point))
    all_elements = By.TAG_NAME, "html"
    wait.until(expected_conditions.visibility_of_all_elements_located(all_elements))
    return driver
sriteja777 commented 7 months ago

@NurlanMirovich If i understand correctly, the issue you are facing is that you are seeing debug logs from BrowserStack SDK in your Github Actions and also locally even though you have not enabled debug logs. Is my understanding correct?

If so, can you please check these things?

Can you please check with above things?

I have tried reproducing the issue with your pytest.ini and log formatter, but wasn't able to reproduce the issue. In my case, I didn't get any debug logs from BrowserStack SDK when I just set as log_level: INFO in pytest.ini

NurlanMirovich commented 7 months ago

Thank you @sriteja777 this is partially worked, BUT i have 19 tests in the actions and for some reason it runs only one and drops the execution, i will add the screenshots of the run please let me know

image image

Visit https://observability.browserstack.com/builds/v01rvvlw5m7t0ndnhzft7spsigams1rz5frw6sef to view build report, insights, and many more debugging information all at one place! 16:51:16 [browserstack_sdk.init][INFO] - Handling session close 16:51:16 [browserstack_sdk.init][INFO] - All done! Error: Process completed with exit code 3.

Here is my pytest ini

image

And BR yaml


# Set BrowserStack Credentials
# =============================
# Add your BrowserStack userName and accessKey here or set BROWSERSTACK_USERNAME and
# BROWSERSTACK_ACCESS_KEY as env variables
userName: ${BROWSERSTACK_USERNAME}
accessKey: ${BROWSERSTACK_ACCESS_KEY}

# ======================
# BrowserStack Reporting
# ======================
# The following capabilities are used to set up reporting on BrowserStack:
# Set 'projectName' to the name of your project. Example, Marketing Website
projectName: python-automation
# Set `buildName` as the name of the job / testsuite being run
buildName: Automation Local run
#sessionName: ${SCENARIO}
# `buildIdentifier` is a unique id to differentiate every execution that gets appended to
# buildName. Choose your buildIdentifier format from the available expressions:
# ${BUILD_NUMBER} (Default): Generates an incremental counter with every execution
# ${DATE_TIME}: Generates a Timestamp with every execution. Eg. 05-Nov-19:30
# Read more about buildIdentifiers here -> https://www.browserstack.com/docs/automate/selenium/organize-tests
buildIdentifier: '#${BUILD_NUMBER}. ${DATE_TIME}' # Supports strings along with either/both ${expression}
# Set `framework` of your test suite. Example, `testng`, `cucumber`, `cucumber-testng`
# This property is needed to send test context to BrowserStack (test name, status)
framework: pytest

# =======================================
# Platforms (Browsers / Devices to test)
# =======================================
# Platforms object contains all the browser / device combinations you want to test on.
# Entire list available here -> (https://www.browserstack.com/list-of-browsers-and-platforms/automate)
platforms:
  - os: OS X
    osVersion: Big Sur
    browserName: Chrome
    browserVersion: latest

# =======================
# Parallels per Platform
# =======================
# The number of parallel threads to be used for each platform set.
# BrowserStack's SDK runner will select the best strategy based on the configured value
#
# Example 1 - If you have configured 3 platforms and set `parallelsPerPlatform` as 2, a total of 6 (2 * 3) parallel threads will be used on BrowserStack
#
# Example 2 - If you have configured 1 platform and set `parallelsPerPlatform` as 5, a total of 5 (1 * 5) parallel threads will be used on BrowserStack
parallelsPerPlatform: 1
idleTimeout: 180
# ==========================================
# BrowserStack Local
# (For localhost, staging/private websites)
# ==========================================
# Set browserStackLocal to true if your website under test is not accessible publicly over the internet
# Learn more about how BrowserStack Local works here -> https://www.browserstack.com/docs/automate/selenium/local-testing-introduction
browserstackLocal: false # <boolean> (Default false)
#browserStackLocalOptions:
#  - localIdentifier: browserstack_local_identifier
testObservability: true
# Options to be passed to BrowserStack local in-case of advanced configurations
  # localIdentifier: # <string> (Default: null) Needed if you need to run multiple instances of local.
  # forceLocal: true  # <boolean> (Default: false) Set to true if you need to resolve all your traffic via BrowserStack Local tunnel.
  # Entire list of arguments available here -> https://www.browserstack.com/docs/automate/selenium/manage-incoming-connections

# ===================
# Debugging features
# ===================
debug: false # <boolean> # Set to true if you need screenshots for every selenium command ran
networkLogs: true # <boolean> Set to true to enable HAR logs capturing
consoleLogs: errors # <string> Remote browser's console debug levels to be printed (Default: errors)
logLevel: info
disableAutoCaptureLogs : true

# Available options are `disable`, `errors`, `warnings`, `info`, `verbose` (Default: errors)```

Here is another errors from the BS logs 

```selenium.common.exceptions.WebDriverException: Message: The property '#/alwaysMatch/bstack:options' contains additional properties ["disableAutoCaptureLogs"] outside of the schema when none are allowed in the payload.
self = <FixtureRequest for <Function test_user_see_transaction_details>>
argname = 'driver'

    def getfixturevalue(self, argname: str) -> Any:
        """Dynamically run a named fixture function.

        Declaring fixtures via function argument is recommended where possible.
        But if you can only decide whether to use another fixture at test
        setup time, you may use this function to retrieve it inside a fixture
        or test function body.

        This method can be used during the test setup phase or the test run
        phase, but during the test teardown phase a fixture's value may not
        be available.

        :param argname:
            The fixture name.
        :raises pytest.FixtureLookupError:
            If the given fixture could not be found.
        """
>       fixturedef = self._get_active_fixturedef(argname)

.venv/lib/python3.12/site-packages/_pytest/fixtures.py:585: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.12/site-packages/_pytest/fixtures.py:607: in _get_active_fixturedef
    self._compute_fixture_value(fixturedef)
.venv/lib/python3.12/site-packages/_pytest/fixtures.py:693: in _compute_fixture_value
    fixturedef.execute(request=subrequest)
.venv/lib/python3.12/site-packages/_pytest/fixtures.py:1069: in execute
    result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
.venv/lib/python3.12/site-packages/pluggy/_hooks.py:501: in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
.venv/lib/python3.12/site-packages/pluggy/_manager.py:119: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
.venv/lib/python3.12/site-packages/_pytest/fixtures.py:1123: in pytest_fixture_setup
    result = call_fixture_func(fixturefunc, request, kwargs)
.venv/lib/python3.12/site-packages/_pytest/fixtures.py:895: in call_fixture_func
    fixture_result = next(generator)
tests/step_defs/conftest.py:100: in driver
    driver = webdriver.Chrome(options)
.venv/lib/python3.12/site-packages/selenium/webdriver/chrome/webdriver.py:45: in __init__
    super().__init__(
.venv/lib/python3.12/site-packages/selenium/webdriver/chromium/webdriver.py:56: in __init__
    super().__init__(
.venv/lib/python3.12/site-packages/pytest_browserstackplugin/plugin.py:827: in bstack1ll111l1_opy_
    bstack1ll1l1l1l_opy_(self, command_executor=command_executor,
.venv/lib/python3.12/site-packages/browserstack_sdk/__init__.py:1186: in bstack1ll111l1_opy_
    bstack1ll1l1l1l_opy_(self, command_executor=command_executor,
.venv/lib/python3.12/site-packages/selenium/webdriver/remote/webdriver.py:208: in __init__
    self.start_session(capabilities)
.venv/lib/python3.12/site-packages/selenium/webdriver/remote/webdriver.py:292: in start_session
    response = self.execute(Command.NEW_SESSION, caps)["value"]
.venv/lib/python3.12/site-packages/bstack_utils/bstack1ll1l1ll1l_opy_.py:29: in execute
    response = self._1lllll11111_opy_(this, driver_command, *args, **kwargs)
.venv/lib/python3.12/site-packages/browserstack_sdk/__init__.py:1117: in bstack1lllllll11_opy_
    response = bstack1llll1111_opy_(self, driver_command, *args, **kwargs)
.venv/lib/python3.12/site-packages/selenium/webdriver/remote/webdriver.py:347: in execute
    self.error_handler.check_response(response)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <selenium.webdriver.remote.errorhandler.ErrorHandler object at 0x7fe073d3b290>
response = {'sessionId': '', 'status': 13, 'value': {'error': 'The property \'#/alwaysMatch/bstack:options\' contains additional ...ontains additional properties ["disableAutoCaptureLogs"] outside of the schema when none are allowed in the payload.'}}

    def check_response(self, response: Dict[str, Any]) -> None:
        """Checks that a JSON response from the WebDriver does not have an
        error.

        :Args:
         - response - The JSON response from the WebDriver server as a dictionary
           object.

        :Raises: If the response contains an error message.
        """
        status = response.get("status", None)
        if not status or status == ErrorCode.SUCCESS:
            return
        value = None
        message = response.get("message", "")
        screen: str = response.get("screen", "")
        stacktrace = None
        if isinstance(status, int):
            value_json = response.get("value", None)
            if value_json and isinstance(value_json, str):
                import json

                try:
                    value = json.loads(value_json)
                    if len(value) == 1:
                        value = value["value"]
                    status = value.get("error", None)
                    if not status:
                        status = value.get("status", ErrorCode.UNKNOWN_ERROR)
                        message = value.get("value") or value.get("message")
                        if not isinstance(message, str):
                            value = message
                            message = message.get("message")
                    else:
                        message = value.get("message", None)
                except ValueError:
                    pass

        exception_class: Type[WebDriverException]
        e = ErrorCode()
        error_codes = [item for item in dir(e) if not item.startswith("__")]
        for error_code in error_codes:
            error_info = getattr(ErrorCode, error_code)
            if isinstance(error_info, list) and status in error_info:
                exception_class = getattr(ExceptionMapping, error_code, WebDriverException)
                break
        else:
            exception_class = WebDriverException

        if not value:
            value = response["value"]
        if isinstance(value, str):
            raise exception_class(value)
        if message == "" and "message" in value:
            message = value["message"]

        screen = None  # type: ignore[assignment]
        if "screen" in value:
            screen = value["screen"]

        stacktrace = None
        st_value = value.get("stackTrace") or value.get("stacktrace")
        if st_value:
            if isinstance(st_value, str):
                stacktrace = st_value.split("\n")
            else:
                stacktrace = []
                try:
                    for frame in st_value:
                        line = frame.get("lineNumber", "")
                        file = frame.get("fileName", "<anonymous>")
                        if line:
                            file = f"{file}:{line}"
                        meth = frame.get("methodName", "<anonymous>")
                        if "className" in frame:
                            meth = f"{frame['className']}.{meth}"
                        msg = "    at %s (%s)"
                        msg = msg % (meth, file)
                        stacktrace.append(msg)
                except TypeError:
                    pass
        if exception_class == UnexpectedAlertPresentException:
            alert_text = None
            if "data" in value:
                alert_text = value["data"].get("text")
            elif "alert" in value:
                alert_text = value["alert"].get("text")
            raise exception_class(message, screen, stacktrace, alert_text)  # type: ignore[call-arg]  # mypy is not smart enough here
>       raise exception_class(message, screen, stacktrace)
E       selenium.common.exceptions.WebDriverException: Message: The property '#/alwaysMatch/bstack:options' contains additional properties ["disableAutoCaptureLogs"] outside of the schema when none are allowed in the payload.

.venv/lib/python3.12/site-packages/selenium/webdriver/remote/errorhandler.py:229: WebDriverException ```
sriteja777 commented 7 months ago

@NurlanMirovich The issue you are facing currently is fixed in 1.19.19 version of browserstack-sdk . Can you please upgrade your browserstack-sdk package to the latest 1.19.24 and try out once?

Let me know if you available for a quick call to resolve this issue faster

NurlanMirovich commented 7 months ago

Hi @sriteja777 i should be able to jump on a call with you... i updated but still minor issues

All of the tests are failing for some reason, and how do i disable the BS logs, i just want those in green

image


Stacktrace:
0   chromedriver                        0x000000010c9d70f8 chromedriver + 4595960
1   chromedriver                        0x000000010c9cee63 chromedriver + 4562531
2   chromedriver                        0x000000010c5d239a chromedriver + 381850
3   chromedriver                        0x000000010c61bf08 chromedriver + 683784
4   chromedriver                        0x000000010c61c191 chromedriver + 684433
5   chromedriver                        0x000000010c660b14 chromedriver + 965396
6   chromedriver                        0x000000010c63e16d chromedriver + 823661
7   chromedriver                        0x000000010c65e14d chromedriver + 954701
8   chromedriver                        0x000000010c63dee3 chromedriver + 823011
9   chromedriver                        0x000000010c60ebe4 chromedriver + 629732
10  chromedriver                        0x000000010c60f79e chromedriver + 632734
11  chromedriver                        0x000000010c99d012 chromedriver + 4358162
12  chromedriver                        0x000000010c9a1c5d chromedriver + 4377693
13  chromedriver                        0x000000010c9a15d3 chromedriver + 4376019
14  chromedriver                        0x000000010c9a1f05 chromedriver + 4378373
15  chromedriver                        0x000000010c986a35 chromedriver + 4266549
16  chromedriver                        0x000000010c9a228d chromedriver + 4379277
17  chromedriver                        0x000000010c979080 chromedriver + 4210816
18  chromedriver                        0x000000010c9bfac8 chromedriver + 4500168
19  chromedriver                        0x000000010c9bfc41 chromedriver + 4500545
20  chromedriver                        0x000000010c9ceaa3 chromedriver + 4561571
21  libsystem_pthread.dylib             0x00007fff204fd8fc _pthread_start + 224
22  libsystem_pthread.dylib             0x00007fff204f9443 thread_start + 15
FAILED tests/step_defs/test_core_app.py::test_recent_transactions_up_to_the_last_5_are_presented_on_the_overview_screen - selenium.common.exceptions.TimeoutException: Message:
FAILED tests/step_defs/test_core_app.py::test_account_info__when_selected_loads_the_account_info_screen_1 - selenium.common.exceptions.TimeoutException: Message: 
Stacktrace:
0   chromedriver                        0x00000001069830f8 chromedriver + 4595960
1   chromedriver                        0x000000010697ae63 chromedriver + 4562531
2   chromedriver                        0x000000010657e39a chromedriver + 381850
3   chromedriver                        0x00000001065c7f08 chromedriver + 683784
4   chromedriver                        0x00000001065c8191 chromedriver + 684433
5   chromedriver                        0x000000010660cb14 chromedriver + 965396
6   chromedriver                        0x00000001065ea16d chromedriver + 823661 ```
NurlanMirovich commented 7 months ago

Ganesh, i have updated the requirements.txt to 23 and tried to run again... also i removed parallelRun from the BS yaml file

image image image image image