Don't create custom elements in main and fix various small issues on tests#747
Conversation
antocuni
left a comment
There was a problem hiding this comment.
Wow, very good debugging/investigation in this PR, thank you!
I am a bit confused about this though:
The PyScriptTest will wait for ===PyScript page fully initialized=== to be printed in the console, once playwright sees the log, it starts the tests and in this case, the clicking. But the script hasn't been added to the head of the page yet, so no event is registered. Here's the relevant code:
I don't understand how this is possible.
(In the following I will put links to the source code in #737 because the logging is better and easier to follow, but the core logic is unchanged).
This is the workflow:
-
PyConfig.connectedCallback()is called, and it callsthis.loadRuntimes()
pyscript/pyscriptjs/src/components/pyconfig.ts
Lines 46 to 50 in 4694602
-
in
loadRuntimes, we create the<script>tag to download pyodide, add aloadevent listener and append it todocument.head:
pyscript/pyscriptjs/src/components/pyconfig.ts
Lines 62 to 73 in 4694602
-
Once the script has been loaded, the
loadevent is fired and we callruntimeObj.initialize() -
runtimeObj.initialize()is this, and at the end it printsPyScript page fully initialized:
pyscript/pyscriptjs/src/runtime.ts
Lines 135 to 181 in 4694602
So, I don't really understand how it is possible that (4) is called before the <script> is added to the head. I also tried to put a console.log after document.head.appendChild(script) and I confirm that it's printed way before PyScript page fully initialized.
So, there must be another reason for why the time.sleep is needed, but I think this investigation should not be a blocker to merge this PR, as it contains a lot of good stuff.
Moreover, I plan to do completely do a refactoring to improve the life cycle of a page and probably the code will change a lot, so maybe it's better to continue the investigation after the refactoring.
|
Thank you for the review and the walkthrough! I was clearly wrong thinking that the appendChild step was the reason why we had to wait a short period of time 🤔 Although I should have added a log statement there, but I forgot 😞 So yeah, It's a mystery still why we need to wait this short time, I'll probably poke at it a bit more to see if I can figure out what's happening 😄 |
e0d4c60 to
29d0884
Compare
|
also, I see that currently the tests are failing because of |
…tests (pyscript#747) * Create custom elements when the runtime finishes loading * Remove xfails and fix repl integration test * Fix commented ignore * Address Antonio's comments * Fix bad rebase * Make ure to wait for repl to be in attached state before asserting content * Move createCustomeElement up so it runs before we close the loader, xfail flaky d3 test * Fix xfail
* Execute pys-on* events when triggered, not at load Mimicing the behavior of Javascripts 'onLoad' event, we should not be executing the use code at page-load time, only when the event is triggered. * Update examples to new syntax * Fix merge issue * Await running event handler code * Restore pys-on* events with original behavior, deprecation warning * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * xfail toga example * Add missing { (typo) * Adjust callback chandling to make linter happy * Change alpha to latest (#760) * Don't create custom elements in main and fix various small issues on tests (#747) * Create custom elements when the runtime finishes loading * Remove xfails and fix repl integration test * Fix commented ignore * Address Antonio's comments * Fix bad rebase * Make ure to wait for repl to be in attached state before asserting content * Move createCustomeElement up so it runs before we close the loader, xfail flaky d3 test * Fix xfail * [pre-commit.ci] pre-commit autoupdate (#762) updates: - [github.com/pre-commit/mirrors-eslint: v8.23.0 → v8.23.1](pre-commit/mirrors-eslint@v8.23.0...v8.23.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * change documentation to point to latest instead of frozen alpha (#764) * Toga example is xpass * Correct 'xpass' to 'xfail' mark Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Peter W <34256109+pww217@users.noreply.github.com> Co-authored-by: Fábio Rosado <fabiorosado@outlook.com>
As the title stays, the main goal of the branch is to kill the infamous runtimeLoaded global store and all the complications, problems and bugs caused by the fact that in many places we needed to ensure/wait that the global runtime was properly set before being able to execute code. The core idea is that runtime is never a global object and that it's passed around explicitly, which means that when a function receives it, it is guaranteed to be initialized&ready. This caused a bit of complications in pybutton.ts, pyinputbox.ts and pyrepl.ts, because they indirectly want to call runtime.run from connectedCallback, which is the only place where we cannot explicitly pass the runtime because it's automatically called by the browser. But also, it is also a sign of a bad design, because it were entirely possible that connectedCallback was called before the runtime was ready, which probably caused many bugs, see e.g. #673 and #747. The solution to is use dependency injection and create the class later on: so instead of having a global PyButton class which relies on a global runtime (whose state is uncertain) we have a make_PyButton function which takes a runtime and make a PyButton class which is tied to that specific runtime (whose state is certainly ready, because we call make_PyButton only when we know that the runtime is ready). Similar for PyInputBox and PyRepl. Other highlights: thanks to this, I could kill the also infamous runAfterRuntimeInitialized and a couple of smelly lines which used setTimeout to "wait" for the runtime. While I was at it, I also called a lot of other stores which were completely unused and where probably leftovers from a past universe.
As the title stays, the main goal of the branch is to kill the infamous runtimeLoaded global store and all the complications, problems and bugs caused by the fact that in many places we needed to ensure/wait that the global runtime was properly set before being able to execute code. The core idea is that runtime is never a global object and that it's passed around explicitly, which means that when a function receives it, it is guaranteed to be initialized&ready. This caused a bit of complications in pybutton.ts, pyinputbox.ts and pyrepl.ts, because they indirectly want to call runtime.run from connectedCallback, which is the only place where we cannot explicitly pass the runtime because it's automatically called by the browser. But also, it is also a sign of a bad design, because it were entirely possible that connectedCallback was called before the runtime was ready, which probably caused many bugs, see e.g. pyscript#673 and pyscript#747. The solution to is use dependency injection and create the class later on: so instead of having a global PyButton class which relies on a global runtime (whose state is uncertain) we have a make_PyButton function which takes a runtime and make a PyButton class which is tied to that specific runtime (whose state is certainly ready, because we call make_PyButton only when we know that the runtime is ready). Similar for PyInputBox and PyRepl. Other highlights: thanks to this, I could kill the also infamous runAfterRuntimeInitialized and a couple of smelly lines which used setTimeout to "wait" for the runtime. While I was at it, I also called a lot of other stores which were completely unused and where probably leftovers from a past universe.
As the title stays, the main goal of the branch is to kill the infamous runtimeLoaded global store and all the complications, problems and bugs caused by the fact that in many places we needed to ensure/wait that the global runtime was properly set before being able to execute code. The core idea is that runtime is never a global object and that it's passed around explicitly, which means that when a function receives it, it is guaranteed to be initialized&ready. This caused a bit of complications in pybutton.ts, pyinputbox.ts and pyrepl.ts, because they indirectly want to call runtime.run from connectedCallback, which is the only place where we cannot explicitly pass the runtime because it's automatically called by the browser. But also, it is also a sign of a bad design, because it were entirely possible that connectedCallback was called before the runtime was ready, which probably caused many bugs, see e.g. pyscript#673 and pyscript#747. The solution to is use dependency injection and create the class later on: so instead of having a global PyButton class which relies on a global runtime (whose state is uncertain) we have a make_PyButton function which takes a runtime and make a PyButton class which is tied to that specific runtime (whose state is certainly ready, because we call make_PyButton only when we know that the runtime is ready). Similar for PyInputBox and PyRepl. Other highlights: thanks to this, I could kill the also infamous runAfterRuntimeInitialized and a couple of smelly lines which used setTimeout to "wait" for the runtime. While I was at it, I also called a lot of other stores which were completely unused and where probably leftovers from a past universe.
As the title stays, the main goal of the branch is to kill the infamous runtimeLoaded global store and all the complications, problems and bugs caused by the fact that in many places we needed to ensure/wait that the global runtime was properly set before being able to execute code. The core idea is that runtime is never a global object and that it's passed around explicitly, which means that when a function receives it, it is guaranteed to be initialized&ready. This caused a bit of complications in pybutton.ts, pyinputbox.ts and pyrepl.ts, because they indirectly want to call runtime.run from connectedCallback, which is the only place where we cannot explicitly pass the runtime because it's automatically called by the browser. But also, it is also a sign of a bad design, because it were entirely possible that connectedCallback was called before the runtime was ready, which probably caused many bugs, see e.g. pyscript#673 and pyscript#747. The solution to is use dependency injection and create the class later on: so instead of having a global PyButton class which relies on a global runtime (whose state is uncertain) we have a make_PyButton function which takes a runtime and make a PyButton class which is tied to that specific runtime (whose state is certainly ready, because we call make_PyButton only when we know that the runtime is ready). Similar for PyInputBox and PyRepl. Other highlights: thanks to this, I could kill the also infamous runAfterRuntimeInitialized and a couple of smelly lines which used setTimeout to "wait" for the runtime. While I was at it, I also called a lot of other stores which were completely unused and where probably leftovers from a past universe.
This PR fixes a few things:
Hopefully, you don't mind this wall of text, explaining the reasoning behind the decisions in the PR 😄
runAfterRuntimeInitialized running immediately on page load
After adding a bunch of
console.tracesI've noticed thatrunAfterRuntimeInitializedwas being called frommain.jswhich was a bit odd, following the code path, it seems that it was being run when we were creating the py-button custom element inconst xPyButton = customElements.define('py-button', PyButton);See the console output for a better visual.Console output with traces
Initially I wrapped the
customElementsin asetTimeoutand waited for 2.5s. This was sufficient most of the time, but there were some odd times that it wasn't. Increasing it to 3s made the button appear after the page loaded, which looked odd.The main.js file will only create elements for
PyScript,PyConfig,PyLoaderandPyEnv. and the rest of the elements will be created once the runtime loads successfully and we closed the loader. With this change, the elements were always rendered successfully on the page and the errorCannot use 'in' operator to search for 'run' in undefinedstopped.Initially, the
createCustomElementswas in theutils.tsbut that caused jest to fail with a strange behaviour, so this function was moved to its own file.Removing the time.sleep from tests
While working on the tests last week (or two?) I added a few
time.sleepbecause events weren't being triggered correctly. After looking into thepy-buttonintegration test and noticing that the clicks weren't registered, I started looking into thePyScriptTestand the PyScript code.The
PyScriptTestwill wait for===PyScript page fully initialized===to be printed in the console, once playwright sees the log, it starts the tests and in this case, the clicking. But the script hasn't been added to the head of the page yet, so no event is registered. Here's the relevant code:pyscript/pyscriptjs/src/components/pyconfig.ts
Line 68 in e31e03a
Adding a small
page.wait_for_timeoutwas enough for the tests to start passing 😄 I'm wondering if perhaps we can make this timeout even smaller?In a few tests, I've also added a
self.page.wait_for_selectorwhich seems to work nicely so playwright will wait until the element is loaded before trying to click or check the contents of the selector.Please let me know if you would like me to change anything here (or if my assumptions/observations are incorrect 😄 )
Fixes: #673, #677, #726