2018-07-24 17:02:49 +00:00
|
|
|
import os
|
2018-08-03 16:48:22 +00:00
|
|
|
from pathlib import Path
|
2018-03-30 14:51:13 +00:00
|
|
|
import time
|
|
|
|
|
2018-08-06 19:01:59 +00:00
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
2018-08-20 17:02:12 +00:00
|
|
|
def test_init(selenium_standalone):
|
2018-08-24 15:43:59 +00:00
|
|
|
assert ('Python initialization complete'
|
|
|
|
in selenium_standalone.logs.splitlines())
|
2018-08-20 17:02:12 +00:00
|
|
|
assert len(selenium_standalone.driver.window_handles) == 1
|
2018-03-30 14:51:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_webbrowser(selenium):
|
|
|
|
selenium.run("import antigravity")
|
|
|
|
time.sleep(2)
|
|
|
|
assert len(selenium.driver.window_handles) == 2
|
|
|
|
|
|
|
|
|
|
|
|
def test_print(selenium):
|
|
|
|
selenium.run("print('This should be logged')")
|
2018-08-24 15:43:59 +00:00
|
|
|
assert 'This should be logged' in selenium.logs.splitlines()
|
2018-04-09 14:39:52 +00:00
|
|
|
|
|
|
|
|
2018-05-23 11:23:49 +00:00
|
|
|
def test_python2js(selenium):
|
|
|
|
assert selenium.run_js('return pyodide.runPython("None") === undefined')
|
|
|
|
assert selenium.run_js('return pyodide.runPython("True") === true')
|
|
|
|
assert selenium.run_js('return pyodide.runPython("False") === false')
|
|
|
|
assert selenium.run_js('return pyodide.runPython("42") === 42')
|
|
|
|
assert selenium.run_js('return pyodide.runPython("3.14") === 3.14')
|
2018-07-26 20:50:12 +00:00
|
|
|
# Need to test all three internal string representations in Python: UCS1,
|
|
|
|
# UCS2 and UCS4
|
|
|
|
assert selenium.run_js(
|
|
|
|
'return pyodide.runPython("\'ascii\'") === "ascii"')
|
|
|
|
assert selenium.run_js(
|
|
|
|
'return pyodide.runPython("\'ιωδιούχο\'") === "ιωδιούχο"')
|
2018-05-23 11:23:49 +00:00
|
|
|
assert selenium.run_js(
|
|
|
|
'return pyodide.runPython("\'碘化物\'") === "碘化物"')
|
|
|
|
assert selenium.run_js(
|
|
|
|
'let x = pyodide.runPython("b\'bytes\'");\n'
|
2018-06-05 10:16:32 +00:00
|
|
|
'return (x instanceof window.Uint8ClampedArray) && '
|
2018-05-23 11:23:49 +00:00
|
|
|
'(x.length === 5) && '
|
|
|
|
'(x[0] === 98)')
|
|
|
|
assert selenium.run_js(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
let x = pyodide.runPython("[1, 2, 3]");
|
|
|
|
return ((x instanceof window.Array) && (x.length === 3) &&
|
|
|
|
(x[0] == 1) && (x[1] == 2) && (x[2] == 3))
|
|
|
|
""")
|
2018-05-23 11:23:49 +00:00
|
|
|
assert selenium.run_js(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
let x = pyodide.runPython("{42: 64}");
|
|
|
|
return (typeof x === "object") && (x[42] === 64)
|
|
|
|
""")
|
2018-05-23 11:23:49 +00:00
|
|
|
assert selenium.run_js(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
let x = pyodide.runPython("open('/foo.txt', 'wb')")
|
|
|
|
return (x.tell() === 0)
|
|
|
|
""")
|
2018-05-23 11:23:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_pythonexc2js(selenium):
|
|
|
|
try:
|
|
|
|
selenium.run_js('return pyodide.runPython("5 / 0")')
|
2018-07-10 00:59:03 +00:00
|
|
|
except selenium.JavascriptException as e:
|
2018-05-23 11:23:49 +00:00
|
|
|
assert('ZeroDivisionError' in str(e))
|
2018-06-24 16:29:46 +00:00
|
|
|
else:
|
|
|
|
assert False, 'Expected exception'
|
2018-05-23 11:23:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_js2python(selenium):
|
|
|
|
selenium.run_js(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
window.jsstring = "碘化物";
|
|
|
|
window.jsnumber0 = 42;
|
|
|
|
window.jsnumber1 = 42.5;
|
|
|
|
window.jsundefined = undefined;
|
|
|
|
window.jsnull = null;
|
|
|
|
window.jstrue = true;
|
|
|
|
window.jsfalse = false;
|
|
|
|
window.jspython = pyodide.pyimport("open");
|
|
|
|
window.jsbytes = new Uint8Array([1, 2, 3]);
|
|
|
|
window.jsfloats = new Float32Array([1, 2, 3]);
|
|
|
|
window.jsobject = new XMLHttpRequest();
|
|
|
|
"""
|
2018-05-23 11:23:49 +00:00
|
|
|
)
|
|
|
|
assert selenium.run(
|
|
|
|
'from js import jsstring\n'
|
|
|
|
'jsstring == "碘化物"')
|
|
|
|
assert selenium.run(
|
|
|
|
'from js import jsnumber0\n'
|
|
|
|
'jsnumber0 == 42')
|
|
|
|
assert selenium.run(
|
|
|
|
'from js import jsnumber1\n'
|
|
|
|
'jsnumber1 == 42.5')
|
|
|
|
assert selenium.run(
|
|
|
|
'from js import jsundefined\n'
|
|
|
|
'jsundefined is None')
|
|
|
|
assert selenium.run(
|
|
|
|
'from js import jstrue\n'
|
|
|
|
'jstrue is True')
|
|
|
|
assert selenium.run(
|
|
|
|
'from js import jsfalse\n'
|
|
|
|
'jsfalse is False')
|
|
|
|
assert selenium.run(
|
|
|
|
'from js import jspython\n'
|
|
|
|
'jspython is open')
|
|
|
|
assert selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
from js import jsbytes
|
|
|
|
((jsbytes.tolist() == [1, 2, 3])
|
|
|
|
and (jsbytes.tobytes() == b"\x01\x02\x03"))
|
|
|
|
""")
|
2018-06-27 14:57:53 +00:00
|
|
|
assert selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
from js import jsfloats
|
|
|
|
import struct
|
|
|
|
expected = struct.pack("fff", 1, 2, 3)
|
|
|
|
(jsfloats.tolist() == [1, 2, 3]) and (jsfloats.tobytes() == expected)
|
|
|
|
""")
|
2018-05-23 11:23:49 +00:00
|
|
|
assert selenium.run(
|
|
|
|
'from js import jsobject\n'
|
|
|
|
'str(jsobject) == "[object XMLHttpRequest]"')
|
|
|
|
|
|
|
|
|
2018-09-07 12:01:58 +00:00
|
|
|
@pytest.mark.parametrize('wasm_heap', (False, True))
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'jstype, pytype',
|
|
|
|
(
|
|
|
|
('Int8Array', 'b'),
|
|
|
|
('Uint8Array', 'B'),
|
|
|
|
('Uint8ClampedArray', 'B'),
|
|
|
|
('Int16Array', 'h'),
|
|
|
|
('Uint16Array', 'H'),
|
|
|
|
('Int32Array', 'i'),
|
|
|
|
('Uint32Array', 'I'),
|
|
|
|
('Float32Array', 'f'),
|
|
|
|
('Float64Array', 'd')))
|
|
|
|
def test_typed_arrays(selenium, wasm_heap, jstype, pytype):
|
|
|
|
if not wasm_heap:
|
|
|
|
selenium.run_js(
|
|
|
|
f'window.array = new {jstype}([1, 2, 3, 4]);\n')
|
|
|
|
else:
|
|
|
|
selenium.run_js(
|
|
|
|
f"""
|
2018-09-19 14:42:49 +00:00
|
|
|
var buffer = pyodide._module._malloc(
|
2018-09-07 12:01:58 +00:00
|
|
|
4 * {jstype}.BYTES_PER_ELEMENT);
|
|
|
|
window.array = new {jstype}(
|
2018-09-19 14:42:49 +00:00
|
|
|
pyodide._module.HEAPU8.buffer, buffer, 4);
|
2018-09-07 12:01:58 +00:00
|
|
|
window.array[0] = 1;
|
|
|
|
window.array[1] = 2;
|
|
|
|
window.array[2] = 3;
|
|
|
|
window.array[3] = 4;
|
|
|
|
""")
|
|
|
|
assert selenium.run(
|
|
|
|
f"""
|
|
|
|
from js import array
|
|
|
|
import struct
|
|
|
|
expected = struct.pack("{pytype*4}", 1, 2, 3, 4)
|
|
|
|
print(array.format, array.tolist(), array.tobytes())
|
|
|
|
((array.format == "{pytype}")
|
|
|
|
and array.tolist() == [1, 2, 3, 4]
|
|
|
|
and array.tobytes() == expected
|
|
|
|
and array.obj._has_bytes() is {not wasm_heap})
|
|
|
|
""")
|
2018-06-29 20:18:29 +00:00
|
|
|
|
|
|
|
|
2018-04-26 15:43:48 +00:00
|
|
|
def test_import_js(selenium):
|
|
|
|
result = selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
from js import window
|
|
|
|
window.title = 'Foo'
|
|
|
|
window.title
|
|
|
|
""")
|
2018-04-26 15:43:48 +00:00
|
|
|
assert result == 'Foo'
|
|
|
|
|
|
|
|
|
2018-05-23 11:23:49 +00:00
|
|
|
def test_pyproxy(selenium):
|
2018-05-04 17:13:56 +00:00
|
|
|
selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
class Foo:
|
|
|
|
bar = 42
|
|
|
|
def get_value(self, value):
|
|
|
|
return value * 64
|
|
|
|
f = Foo()
|
|
|
|
"""
|
2018-05-09 18:59:55 +00:00
|
|
|
)
|
|
|
|
assert selenium.run_js("return pyodide.pyimport('f').get_value(2)") == 128
|
2018-05-04 17:13:56 +00:00
|
|
|
assert selenium.run_js("return pyodide.pyimport('f').bar") == 42
|
2018-06-15 16:30:22 +00:00
|
|
|
assert selenium.run_js("return ('bar' in pyodide.pyimport('f'))")
|
2018-05-04 17:13:56 +00:00
|
|
|
selenium.run_js("f = pyodide.pyimport('f'); f.baz = 32")
|
|
|
|
assert selenium.run("f.baz") == 32
|
|
|
|
assert set(selenium.run_js(
|
|
|
|
"return Object.getOwnPropertyNames(pyodide.pyimport('f'))")) == set(
|
2018-05-23 11:23:49 +00:00
|
|
|
['__class__', '__delattr__', '__dict__', '__dir__',
|
2018-05-04 17:13:56 +00:00
|
|
|
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
|
|
|
|
'__gt__', '__hash__', '__init__', '__init_subclass__', '__le__',
|
|
|
|
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
|
|
|
|
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
|
|
|
|
'__str__', '__subclasshook__', '__weakref__', 'bar', 'baz',
|
2018-07-09 21:15:10 +00:00
|
|
|
'get_value', 'toString', 'prototype', 'arguments', 'caller'])
|
2018-06-15 16:30:22 +00:00
|
|
|
assert selenium.run("hasattr(f, 'baz')")
|
2018-05-04 17:13:56 +00:00
|
|
|
selenium.run_js("delete pyodide.pyimport('f').baz")
|
2018-06-15 16:30:22 +00:00
|
|
|
assert not selenium.run("hasattr(f, 'baz')")
|
|
|
|
assert selenium.run_js(
|
|
|
|
"return pyodide.pyimport('f').toString()").startswith('<Foo')
|
2018-05-04 17:13:56 +00:00
|
|
|
|
|
|
|
|
2018-06-24 16:29:46 +00:00
|
|
|
def test_pyproxy_destroy(selenium):
|
|
|
|
selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
class Foo:
|
|
|
|
bar = 42
|
|
|
|
def get_value(self, value):
|
|
|
|
return value * 64
|
|
|
|
f = Foo()
|
|
|
|
"""
|
2018-06-24 16:29:46 +00:00
|
|
|
)
|
|
|
|
try:
|
|
|
|
selenium.run_js(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
let f = pyodide.pyimport('f');
|
|
|
|
console.assert(f.get_value(1) === 64);
|
|
|
|
f.destroy();
|
|
|
|
f.get_value();
|
|
|
|
""")
|
2018-07-10 00:59:03 +00:00
|
|
|
except selenium.JavascriptException as e:
|
2018-06-25 13:09:16 +00:00
|
|
|
assert 'Object has already been destroyed' in str(e)
|
2018-06-24 16:29:46 +00:00
|
|
|
else:
|
|
|
|
assert False, 'Expected exception'
|
|
|
|
|
|
|
|
|
2018-05-23 11:23:49 +00:00
|
|
|
def test_jsproxy(selenium):
|
2018-05-10 14:37:46 +00:00
|
|
|
assert selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
from js import document
|
|
|
|
el = document.createElement('div')
|
|
|
|
document.body.appendChild(el)
|
|
|
|
document.body.children.length"""
|
2018-05-10 14:37:46 +00:00
|
|
|
) == 1
|
|
|
|
assert selenium.run(
|
|
|
|
"document.body.children[0].tagName") == 'DIV'
|
2018-05-23 11:23:49 +00:00
|
|
|
assert selenium.run(
|
|
|
|
"repr(document)") == '[object HTMLDocument]'
|
2018-06-05 00:37:40 +00:00
|
|
|
selenium.run_js(
|
|
|
|
"window.square = function (x) { return x*x; }")
|
|
|
|
assert selenium.run(
|
|
|
|
"from js import square\n"
|
|
|
|
"square(2)") == 4
|
|
|
|
assert selenium.run(
|
|
|
|
"from js import ImageData\n"
|
|
|
|
"ImageData.new(64, 64)")
|
2018-06-25 20:54:08 +00:00
|
|
|
assert selenium.run(
|
|
|
|
"from js import ImageData\n"
|
|
|
|
"ImageData.typeof") == 'function'
|
|
|
|
selenium.run_js(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
class Point {
|
|
|
|
constructor(x, y) {
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
window.TEST = new Point(42, 43);""")
|
2018-06-25 20:54:08 +00:00
|
|
|
assert selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
from js import TEST
|
|
|
|
del TEST.y
|
|
|
|
TEST.y""") is None
|
2018-06-25 20:54:08 +00:00
|
|
|
selenium.run_js(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
class Point {
|
|
|
|
constructor(x, y) {
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
window.TEST = new Point(42, 43);""")
|
2018-06-25 20:54:08 +00:00
|
|
|
assert selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
from js import TEST
|
|
|
|
del TEST['y']
|
|
|
|
TEST['y']""") is None
|
2018-06-25 20:54:08 +00:00
|
|
|
assert selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
from js import TEST
|
|
|
|
TEST == TEST
|
|
|
|
""")
|
2018-06-25 20:54:08 +00:00
|
|
|
assert selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
from js import TEST
|
|
|
|
TEST != 'foo'
|
|
|
|
""")
|
2018-06-25 20:54:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_jsproxy_iter(selenium):
|
|
|
|
selenium.run_js(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
function makeIterator(array) {
|
|
|
|
var nextIndex = 0;
|
|
|
|
return {
|
|
|
|
next: function() {
|
|
|
|
return nextIndex < array.length ?
|
|
|
|
{value: array[nextIndex++], done: false} :
|
|
|
|
{done: true};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
window.ITER = makeIterator([1, 2, 3]);""")
|
2018-06-25 20:54:08 +00:00
|
|
|
assert selenium.run(
|
|
|
|
"from js import ITER\n"
|
|
|
|
"list(ITER)") == [1, 2, 3]
|
2018-05-10 14:37:46 +00:00
|
|
|
|
2018-05-09 18:33:52 +00:00
|
|
|
|
|
|
|
def test_open_url(selenium):
|
|
|
|
assert selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
import pyodide
|
2018-09-12 14:14:51 +00:00
|
|
|
pyodide.open_url('test/data.txt').read()
|
|
|
|
""") == 'HELLO\n'
|
|
|
|
|
|
|
|
|
|
|
|
def test_open_url_cgi(selenium):
|
|
|
|
assert selenium.run(
|
|
|
|
"""
|
|
|
|
import pyodide
|
|
|
|
pyodide.open_url('test/data.cgi').read()
|
2018-09-07 12:01:58 +00:00
|
|
|
""") == 'HELLO\n'
|
2018-05-09 18:33:52 +00:00
|
|
|
|
|
|
|
|
2018-08-17 09:33:58 +00:00
|
|
|
def test_run_core_python_test(python_test, selenium, request):
|
2018-08-20 15:38:01 +00:00
|
|
|
|
2018-08-17 09:33:58 +00:00
|
|
|
name, error_flags = python_test
|
2018-08-20 15:38:01 +00:00
|
|
|
|
2018-08-17 09:39:04 +00:00
|
|
|
if ('crash' in error_flags or
|
2018-08-20 15:38:01 +00:00
|
|
|
'crash-' + selenium.browser in error_flags):
|
2018-08-22 09:42:22 +00:00
|
|
|
pytest.xfail(reason='known failure with code "{}"'
|
|
|
|
.format(','.join(error_flags)))
|
2018-08-17 09:39:46 +00:00
|
|
|
|
|
|
|
selenium.load_package('test')
|
2018-05-30 17:59:09 +00:00
|
|
|
try:
|
|
|
|
selenium.run(
|
2018-09-07 12:01:58 +00:00
|
|
|
"""
|
|
|
|
from test.libregrtest import main
|
|
|
|
try:
|
|
|
|
main(['{}'], verbose=True, verbose3=True)
|
|
|
|
except SystemExit as e:
|
|
|
|
if e.code != 0:
|
|
|
|
raise RuntimeError(f'Failed with code: {{e.code}}')
|
|
|
|
""".format(name))
|
2018-07-10 00:59:03 +00:00
|
|
|
except selenium.JavascriptException as e:
|
2018-07-18 13:26:18 +00:00
|
|
|
print(selenium.logs)
|
|
|
|
raise
|
2018-04-09 14:39:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
def pytest_generate_tests(metafunc):
|
|
|
|
if 'python_test' in metafunc.fixturenames:
|
|
|
|
test_modules = []
|
2018-08-17 09:33:58 +00:00
|
|
|
test_modules_ids = []
|
2018-07-18 22:27:48 +00:00
|
|
|
if 'CIRCLECI' not in os.environ or True:
|
2018-06-24 16:05:21 +00:00
|
|
|
with open(
|
2018-08-06 14:01:28 +00:00
|
|
|
Path(__file__).parent / "python_tests.txt") as fp:
|
2018-06-24 16:05:21 +00:00
|
|
|
for line in fp:
|
|
|
|
line = line.strip()
|
2018-08-17 09:33:58 +00:00
|
|
|
if line.startswith('#') or not line:
|
2018-06-24 16:05:21 +00:00
|
|
|
continue
|
2018-08-17 09:33:58 +00:00
|
|
|
error_flags = line.split()
|
|
|
|
name = error_flags.pop(0)
|
|
|
|
if (not error_flags
|
|
|
|
or set(error_flags).intersection(
|
|
|
|
{'crash', 'crash-chrome', 'crash-firefox'})):
|
|
|
|
test_modules.append((name, error_flags))
|
|
|
|
# explicitly define test ids to keep
|
|
|
|
# a human readable test name in pytest
|
|
|
|
test_modules_ids.append(name)
|
|
|
|
metafunc.parametrize("python_test", test_modules,
|
|
|
|
ids=test_modules_ids)
|
2018-07-23 15:09:16 +00:00
|
|
|
|
|
|
|
|
2018-07-26 20:50:12 +00:00
|
|
|
def test_load_package_after_convert_string(selenium):
|
|
|
|
"""
|
|
|
|
See #93.
|
|
|
|
"""
|
|
|
|
selenium.run(
|
|
|
|
"import sys\n"
|
|
|
|
"x = sys.version")
|
|
|
|
selenium.run_js(
|
|
|
|
"var x = pyodide.pyimport('x')\n"
|
|
|
|
"console.log(x)")
|
|
|
|
selenium.load_package('kiwisolver')
|
|
|
|
selenium.run(
|
|
|
|
"import kiwisolver")
|
2018-09-14 14:58:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_version_info(selenium):
|
|
|
|
from distutils.version import LooseVersion
|
|
|
|
|
|
|
|
version_py_str = selenium.run("""
|
|
|
|
import pyodide
|
|
|
|
|
|
|
|
pyodide.__version__
|
|
|
|
""")
|
|
|
|
version_py = LooseVersion(version_py_str)
|
|
|
|
assert version_py > LooseVersion('0.0.1')
|
|
|
|
|
|
|
|
version_js_str = selenium.run_js("return pyodide.version()")
|
|
|
|
version_js = LooseVersion(version_js_str)
|
|
|
|
assert version_py == version_js
|
2018-09-12 21:17:42 +00:00
|
|
|
|
|
|
|
|
2018-09-17 19:35:54 +00:00
|
|
|
def test_recursive_list(selenium_standalone):
|
|
|
|
selenium_standalone.run(
|
2018-09-12 21:17:42 +00:00
|
|
|
"""
|
|
|
|
x = []
|
|
|
|
x.append(x)
|
|
|
|
"""
|
|
|
|
)
|
2018-09-17 19:35:54 +00:00
|
|
|
selenium_standalone.run_js("x = pyodide.pyimport('x')")
|
2018-09-12 21:17:42 +00:00
|
|
|
|
|
|
|
|
2018-09-17 19:35:54 +00:00
|
|
|
def test_recursive_dict(selenium_standalone):
|
|
|
|
selenium_standalone.run(
|
2018-09-12 21:17:42 +00:00
|
|
|
"""
|
|
|
|
x = {}
|
|
|
|
x[0] = x
|
|
|
|
"""
|
|
|
|
)
|
2018-09-17 19:35:54 +00:00
|
|
|
selenium_standalone.run_js("x = pyodide.pyimport('x')")
|