Repository: pyscript/pyscript
File: pyscriptjs/tests/integration/support.py (Python)

Code before:

    def init_page(self, page):
        self.page = page
        # cache to store data via sha256(url)
        cache = {}

        # router to cache with fail 2x on requests

        def router(route):
            # hash of url
            hash = hashlib.sha256(route.request.url.encode("utf-8")).hexdigest()
            # cached?
            if hash in cache:
                # fulfill via cache
                route.fulfill(status=200, response=cache.get(hash))
            else:
                try:
                    # fetch, cache, fulfill 1x
                    response = page.request.fetch(route.request)
                    cache[hash] = response
                    route.fulfill(status=200, response=response)
                except Exception:
                    # fetch, cache, fulfill 2x
                    response = page.request.fetch(route.request)
                    cache[hash] = response
                    route.fulfill(status=200, response=response)

        # route all urls through router
        self.page.route("**", router)
        self.console = ConsoleMessageCollection(self.logger)
        self._page_errors = []
        page.on("console", self.console.add_message)
        page.on("pageerror", self._on_pageerror)

    def teardown_method(self):
        # we call check_errors on teardown: this means that if there are still
        # non-cleared errors, the test will fail. If you expect errors in your
        # page and they should not cause the test to fail, you should call
        # self.check_errors() in the test itself.
        self.check_errors()

    def _on_pageerror(self, error):
        self.logger.log("JS exception", error.stack, color="red")
        self._page_errors.append(error)

    def check_errors(self):
        """
        Check whether JS errors were reported.

        If it finds a single JS error, raise JsError.
        If it finds multiple JS errors, raise JsMultipleErrors.

Review comment (performance):
In my opinion, the snippets under try and catch are exactly the same, so it makes sense to make a function out of this and put it inside a retry decorator.

So, for example:

```
@retry(times=2, exceptions=(Exception,))
def fetch_and_put_in_cache():
    response = page.request.fetch(route.request)
    cache[hash] = response
    route.fulfill(status=200, response=response)
```

```
def retry(times, exceptions):
    def decorator(func):
        def newfn(*args, **kwargs):
            attempt = 0
            while attempt < times:
                try:
                    return func(*args, **kwargs)
                except exceptions:
                    logging.warning(
                        f"Exception thrown when attempting to run {func} at attempt {attempt} of {times}, retrying..."
                    )
                    attempt += 1
            return func(*args, **kwargs)

        return newfn

    return decorator
```

Code after:
                "the execution of the test pauses here to give you the time to inspect\n"
                "the browser. When you are done, type one of the following commands:\n"
                "    (Pdb) continue\n"
                "    (Pdb) cont\n"
                "    (Pdb) c\n"
            )
            pdb.set_trace()

    def init_page(self, page):
        self.page = page

        # set default timeout to 60000 millliseconds from 30000
        page.set_default_timeout(60000)

        self.console = ConsoleMessageCollection(self.logger)
        self._page_errors = []
        page.on("console", self.console.add_message)
        page.on("pageerror", self._on_pageerror)

    def teardown_method(self):
        # we call check_errors on teardown: this means that if there are still
        # non-cleared errors, the test will fail. If you expect errors in your
        # page and they should not cause the test to fail, you should call
        # self.check_errors() in the test itself.
        self.check_errors()

    def _on_pageerror(self, error):
        self.logger.log("JS exception", error.stack, color="red")
        self._page_errors.append(error)

    def check_errors(self):
        """
        Check whether JS errors were reported.

        If it finds a single JS error, raise JsError.
        If it finds multiple JS errors, raise JsMultipleErrors.

        Upon return, all the errors are cleared, so a subsequent call to
        check_errors will not raise, unless NEW JS errors have been reported
        in the meantime.
        """
        exc = None
        if len(self._page_errors) == 1:
            # if there is a single error, wrap it
            exc = JsError(self._page_errors[0])
        elif len(self._page_errors) >= 2:
            exc = JsMultipleErrors(self._page_errors)
        self._page_errors = []
        if exc:
            raise exc