template.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. import asyncio
  2. import json
  3. import os
  4. import threading
  5. from functools import partial
  6. from os import path
  7. import time
  8. from percy import percySnapshot
  9. from selenium.webdriver import Chrome
  10. from selenium.webdriver.support.ui import Select
  11. from pywebio.input import *
  12. from pywebio.output import *
  13. from pywebio.session import *
  14. here_dir = path.dirname(path.abspath(__file__))
  15. def get_visible_form(browser):
  16. forms = browser.find_elements_by_css_selector('#input-container > div')
  17. for f in forms:
  18. if f.is_displayed():
  19. return f
  20. def basic_output():
  21. set_anchor('top')
  22. for i in range(3):
  23. put_text('text_%s' % i)
  24. put_text('测试空格:20空格:[%s]结束' % (' ' * 20))
  25. for i in range(3):
  26. put_text('inline_text_%s' % i, inline=True)
  27. put_markdown("""### put_markdown 测试
  28. `行内代码`
  29. 无序列表:
  30. - 北京
  31. - 上海
  32. - 天津
  33. 有序列表:
  34. 1. 北京
  35. 2. 上海
  36. 3. 天津
  37. [链接](./#)
  38. ~~删除线~~
  39. """, lstrip=True, anchor='put_markdown')
  40. put_text('<hr/>:')
  41. put_html("<hr/>", anchor='put_html')
  42. put_text('code:')
  43. put_code(json.dumps(dict(name='pywebio', author='wangweimin'), indent=4), 'json', anchor='put_code')
  44. put_text('table:')
  45. put_table([
  46. ['Name', 'Gender', 'Address'],
  47. ['Wang', 'M', 'China'],
  48. ['Liu', 'W', 'America'],
  49. ])
  50. put_table([
  51. ['Wang', 'M', 'China'],
  52. ['Liu', 'W', 'America'],
  53. ], header=['Name', 'Gender', 'Address'])
  54. put_table([
  55. {"Course": "OS", "Score": "80"},
  56. {"Course": "DB", "Score": "93"},
  57. ], header=["Course", "Score"], anchor='put_table')
  58. def edit_row(choice, row):
  59. put_text("You click %s button at row %s" % (choice, row), after='table_cell_buttons')
  60. put_table([
  61. ['Idx', 'Actions'],
  62. ['1', table_cell_buttons(['edit', 'delete'], onclick=partial(edit_row, row=1))],
  63. ['2', table_cell_buttons(['edit', 'delete'], onclick=partial(edit_row, row=2))],
  64. ['3', table_cell_buttons(['edit', 'delete'], onclick=partial(edit_row, row=3))],
  65. ], anchor='table_cell_buttons')
  66. put_buttons(['A', 'B', 'C'], onclick=partial(put_text, after='put_buttons'), anchor='put_buttons')
  67. img_data = open(path.join(here_dir, 'assets', 'img.png'), 'rb').read()
  68. put_image(img_data, anchor='put_image1')
  69. put_image(img_data, width="30px", anchor='put_image2')
  70. put_image(img_data, height="50px", anchor='put_image3')
  71. put_file('hello_word.txt', b'hello word!', anchor='put_file')
  72. put_markdown('### 锚点')
  73. put_text('anchor A1', anchor='A1')
  74. put_text('new anchor A1', anchor='A1')
  75. put_text('anchor A2', anchor='A2')
  76. put_text('anchor A3', anchor='A3')
  77. put_text('after=A1', after='A1')
  78. put_text('after=A2', after='A2')
  79. put_text('before=A1', before='A1')
  80. put_text('before=A3', before='A3')
  81. put_text('after=A3', after='A3')
  82. clear_range('A1', "A2")
  83. clear_range('A3', 'A2')
  84. clear_after('A3')
  85. put_text('before=top', before='top')
  86. clear_before('top')
  87. put_text('before=top again', before='top')
  88. put_text('to remove', anchor='to_remove')
  89. remove('to_remove')
  90. def background_output():
  91. put_text("Background output", anchor='background')
  92. def background():
  93. for i in range(20):
  94. put_text('%s ' % i, inline=True, after='background')
  95. t = threading.Thread(target=background)
  96. register_thread(t)
  97. t.start()
  98. async def coro_background_output():
  99. put_text("Background output", anchor='background')
  100. async def background():
  101. for i in range(20):
  102. put_text('%s ' % i, inline=True, after='background')
  103. return run_async(background())
  104. def test_output(browser: Chrome, percy_prefix=''):
  105. """测试输出::
  106. template.basic_output()
  107. template.background_output() # 或者 await template.coro_background_output()
  108. hold()
  109. """
  110. time.sleep(1) # 等待输出完毕
  111. if percy_prefix:
  112. percy_prefix = percy_prefix + ' '
  113. tab_btns = browser.find_elements_by_css_selector('#pywebio-anchor-table_cell_buttons button')
  114. for btn in tab_btns:
  115. time.sleep(0.5)
  116. btn.click()
  117. btns = browser.find_elements_by_css_selector('#pywebio-anchor-put_buttons button')
  118. for btn in btns:
  119. time.sleep(0.5)
  120. btn.click()
  121. time.sleep(1)
  122. percySnapshot(browser=browser, name=percy_prefix + 'basic output')
  123. def basic_input():
  124. age = yield input("How old are you?", type=NUMBER)
  125. put_markdown(f'`{repr(age)}`')
  126. password = yield input("Input password", type=PASSWORD)
  127. put_markdown(f'`{repr(password)}`')
  128. # 下拉选择框
  129. gift = yield select('Which gift you want?', ['keyboard', 'ipad'])
  130. put_markdown(f'`{repr(gift)}`')
  131. # CheckBox
  132. agree = yield checkbox("用户协议", options=['I agree to terms and conditions'])
  133. put_markdown(f'`{repr(agree)}`')
  134. # Text Area
  135. text = yield textarea('Text Area', rows=3, placeholder='Some text')
  136. put_markdown(f'`{repr(text)}`')
  137. # 文件上传
  138. img = yield file_upload("Select a image:", accept="image/*")
  139. put_markdown(f'`{repr(img)}`')
  140. # 输入参数
  141. res = yield input('This is label', type=TEXT, placeholder='This is placeholder,required=True',
  142. help_text='This is help text', required=True)
  143. put_markdown(f'`{repr(res)}`')
  144. # 校验函数
  145. def check_age(p): # 检验函数校验通过时返回None,否则返回错误消息
  146. if p < 10:
  147. return 'Too young!!'
  148. if p > 60:
  149. return 'Too old!!'
  150. age = yield input("How old are you?", type=NUMBER, valid_func=check_age, help_text='age in [10, 60]')
  151. put_markdown(f'`{repr(age)}`')
  152. # Codemirror
  153. code = yield textarea('Code Edit', code={
  154. 'mode': "python", # 编辑区代码语言
  155. 'theme': 'darcula', # 编辑区darcula主题, Visit https://codemirror.net/demo/theme.html#cobalt to get more themes
  156. }, value='import something\n# Write your python code')
  157. put_markdown(f'`{repr(code)}`')
  158. # 输入组
  159. info = yield input_group("Cancelable", [
  160. input('Input your name', name='name'),
  161. input('Input your age', name='age', type=NUMBER, valid_func=check_age, help_text='age in [10, 60]')
  162. ], cancelable=True)
  163. put_markdown(f'`{repr(info)}`')
  164. def check_form(data): # 检验函数校验通过时返回None,否则返回 (input name,错误消息)
  165. if len(data['password']) > 6:
  166. return ('password', 'password太长!')
  167. check_item_data = []
  168. def check_item(data):
  169. check_item_data.append(repr(data))
  170. info = yield input_group('Input group', [
  171. input('Text', type=TEXT, datalist=['data-%s' % i for i in range(10)], name='text',
  172. required=True, help_text='required=True', valid_func=check_item),
  173. input('Number', type=NUMBER, value="42", name='number', valid_func=check_item),
  174. input('Float', type=FLOAT, name='float', valid_func=check_item),
  175. input('Password', type=PASSWORD, name='password', valid_func=check_item),
  176. textarea('Textarea', rows=3, maxlength=20, name='textarea',
  177. help_text='rows=3, maxlength=20', valid_func=check_item),
  178. textarea('Code', name='code', code={
  179. 'lineNumbers': False,
  180. 'indentUnit': 2,
  181. }, value='import something\n# Write your python code', valid_func=check_item),
  182. select('select-multiple', [
  183. {'label': '标签0,selected', 'value': '0', 'selected': True},
  184. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  185. ('标签2,selected', '2', True),
  186. ('标签3', '3'),
  187. ('标签4,disabled', '4', False, True),
  188. '标签5,selected',
  189. ], name='select-multiple', multiple=True, value=['标签5,selected'], required=True,
  190. help_text='required至少选择一项', valid_func=check_item),
  191. select('select', [
  192. {'label': '标签0', 'value': '0', 'selected': False},
  193. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  194. ('标签2', '2', False),
  195. ('标签3', '3'),
  196. ('标签4,disabled', '4', False, True),
  197. '标签5,selected',
  198. ], name='select', value=['标签5,selected'], valid_func=check_item),
  199. checkbox('checkbox-inline', [
  200. {'label': '标签0,selected', 'value': '0', 'selected': False},
  201. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  202. ('标签2,selected', '2', True),
  203. ('标签3', '3'),
  204. ('标签4,disabled', '4', False, True),
  205. '标签5,selected',
  206. ], inline=True, name='checkbox-inline', value=['标签5,selected', '标签0', '标签0,selected'], valid_func=check_item),
  207. checkbox('checkbox', [
  208. {'label': '标签0,selected', 'value': '0', 'selected': True},
  209. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  210. ('标签2,selected', '2', True),
  211. ('标签3', '3'),
  212. ('标签4,disabled', '4', False, True),
  213. '标签5',
  214. ], name='checkbox', valid_func=check_item),
  215. radio('radio-inline', [
  216. {'label': '标签0', 'value': '0', 'selected': False},
  217. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  218. ('标签2', '2', False),
  219. ('标签3', '3'),
  220. ('标签4,disabled', '4', False, True),
  221. '标签5,selected',
  222. ], inline=True, name='radio-inline', value='标签5,selected', valid_func=check_item),
  223. radio('radio', [
  224. {'label': '标签0', 'value': '0', 'selected': False},
  225. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  226. ('标签2', '2', False),
  227. ('标签3', '3'),
  228. ('标签4,disabled', '4', False, True),
  229. '标签5,selected',
  230. ], inline=False, name='radio', value='标签5,selected', valid_func=check_item),
  231. file_upload('file_upload', name='file_upload'),
  232. actions('actions', [
  233. {'label': '提交', 'value': 'submit'},
  234. ('提交2', 'submit2'),
  235. '提交3',
  236. {'label': 'disabled', 'disabled': True},
  237. ('重置', 'reset', 'reset'),
  238. {'label': '取消', 'type': 'cancel'},
  239. ], name='actions', help_text='actions'),
  240. ], valid_func=check_form)
  241. put_text('`valid_func()` log:')
  242. put_code(json.dumps(sorted(check_item_data), indent=4, ensure_ascii=False), 'json')
  243. put_text('Form result:')
  244. if info:
  245. put_code(json.dumps([repr(i) for i in sorted(info.items())], indent=4, ensure_ascii=False), 'json')
  246. # yield actions(['Continue'])
  247. def background_input():
  248. def background():
  249. time.sleep(1)
  250. res = input('background')
  251. put_markdown(f'`background: {repr(res)}`')
  252. t = threading.Thread(target=background)
  253. register_thread(t)
  254. t.start()
  255. res = input('front')
  256. put_markdown(f'`front: {repr(res)}`')
  257. async def coro_background_input():
  258. async def background():
  259. await asyncio.sleep(1)
  260. res = await input('background')
  261. put_markdown(f'`background: {repr(res)}`')
  262. run_async(background())
  263. res = await input('front')
  264. put_markdown(f'`front: {repr(res)}`')
  265. async def flask_coro_background_input():
  266. async def background():
  267. await run_asyncio_coroutine(asyncio.sleep(1))
  268. res = await input('background')
  269. put_markdown(f'`background: {repr(res)}`')
  270. run_async(background())
  271. res = await input('front')
  272. put_markdown(f'`front: {repr(res)}`')
  273. def test_input(browser: Chrome, percy_prefix=''):
  274. """测试输入::
  275. template.basic_input()
  276. actions(['Continue'])
  277. template.background_input() # 或者 await template.coro_background_input() / flask_coro_background_input
  278. """
  279. if percy_prefix:
  280. percy_prefix = percy_prefix + ' '
  281. browser.find_element_by_css_selector('input').send_keys("22")
  282. browser.find_element_by_tag_name('form').submit()
  283. time.sleep(0.5)
  284. browser.find_element_by_css_selector('input').send_keys("secret")
  285. browser.find_element_by_tag_name('form').submit()
  286. time.sleep(0.5)
  287. browser.find_element_by_tag_name('form').submit()
  288. # checkbox
  289. time.sleep(0.5)
  290. browser.find_element_by_css_selector('input').click()
  291. browser.find_element_by_tag_name('form').submit()
  292. # Text Area
  293. time.sleep(0.5)
  294. browser.find_element_by_css_selector('textarea').send_keys(" ".join(str(i) for i in range(20)))
  295. browser.find_element_by_tag_name('form').submit()
  296. # file
  297. time.sleep(0.5)
  298. img_path = path.join(here_dir, 'assets', 'img.png')
  299. browser.find_element_by_css_selector('input').send_keys(img_path)
  300. browser.find_element_by_tag_name('form').submit()
  301. # text
  302. time.sleep(0.5)
  303. browser.find_element_by_css_selector('input').send_keys("text")
  304. browser.find_element_by_tag_name('form').submit()
  305. # valid func, age in [10, 60]
  306. time.sleep(0.5)
  307. browser.find_element_by_css_selector('input').send_keys("1")
  308. browser.find_element_by_tag_name('form').submit()
  309. time.sleep(0.5)
  310. browser.find_element_by_css_selector('input').clear()
  311. browser.find_element_by_css_selector('input').send_keys("90")
  312. browser.find_element_by_tag_name('form').submit()
  313. time.sleep(0.5)
  314. browser.find_element_by_css_selector('input').clear()
  315. browser.find_element_by_css_selector('input').send_keys("23")
  316. browser.find_element_by_tag_name('form').submit()
  317. # code
  318. time.sleep(0.5)
  319. # browser.find_element_by_css_selector('textarea').send_keys(" ".join(str(i) for i in range(20)))
  320. browser.find_element_by_tag_name('form').submit()
  321. # Cancelable from group
  322. time.sleep(0.5)
  323. browser.find_element_by_css_selector('input[name="name"]').send_keys("name")
  324. browser.find_element_by_css_selector('input[name="age"]').send_keys("90")
  325. browser.find_element_by_tag_name('form').submit()
  326. percySnapshot(browser=browser, name=percy_prefix + 'input group invalid')
  327. browser.find_element_by_css_selector('input[name="age"]').clear()
  328. browser.find_element_by_css_selector('input[name="age"]').send_keys("23")
  329. browser.find_element_by_tag_name('form').submit()
  330. # Input group
  331. time.sleep(0.5)
  332. percySnapshot(browser=browser, name=percy_prefix + 'input group all')
  333. browser.find_element_by_css_selector('input[name="text"]').send_keys("name")
  334. browser.find_element_by_css_selector('input[name="number"]').send_keys("20")
  335. browser.find_element_by_css_selector('input[name="float"]').send_keys("3.1415")
  336. browser.find_element_by_css_selector('input[name="password"]').send_keys("password")
  337. browser.find_element_by_css_selector('textarea[name="textarea"]').send_keys(" ".join(str(i) for i in range(20)))
  338. # browser.find_element_by_css_selector('[name="code"]').send_keys(" ".join(str(i) for i in range(10)))
  339. Select(browser.find_element_by_css_selector('select[name="select-multiple"]')).select_by_index(0)
  340. # browser. find_element_by_css_selector('[name="select"]'). send_keys("name")
  341. # browser. find_element_by_css_selector('[name="checkbox-inline"]'). send_keys("name")
  342. # browser. find_element_by_css_selector('[name="checkbox"]'). send_keys("name")
  343. # browser. find_element_by_css_selector('[name="radio-inline"]'). send_keys("name")
  344. # browser. find_element_by_css_selector('[name="radio"]'). send_keys("name")
  345. browser.find_element_by_css_selector('input[name="file_upload"]').send_keys(img_path)
  346. browser.find_element_by_css_selector('button[value="submit2"]').click()
  347. time.sleep(0.5)
  348. percySnapshot(browser=browser, name=percy_prefix + 'input group all invalid')
  349. browser.find_element_by_css_selector('input[name="password"]').clear()
  350. browser.find_element_by_css_selector('input[name="password"]').send_keys("123")
  351. browser.find_element_by_css_selector('button[value="submit2"]').click()
  352. time.sleep(0.5)
  353. percySnapshot(browser=browser, name=percy_prefix + 'input group all submit')
  354. browser.find_element_by_css_selector('form').submit()
  355. # background
  356. time.sleep(3)
  357. get_visible_form(browser).find_element_by_css_selector('input').send_keys("background")
  358. get_visible_form(browser).find_element_by_tag_name('form').submit()
  359. # front
  360. time.sleep(0.5)
  361. get_visible_form(browser).find_element_by_css_selector('input').send_keys("front")
  362. get_visible_form(browser).find_element_by_tag_name('form').submit()
  363. def set_defer_call():
  364. def deferred_1():
  365. open('test_defer.tmp', 'w').write('deferred_1')
  366. def deferred_2():
  367. open('test_defer.tmp', 'a').write('deferred_2')
  368. defer_call(deferred_1)
  369. defer_call(deferred_2)
  370. def test_defer_call():
  371. output = open('test_defer.tmp').read()
  372. assert "deferred_1" in output
  373. assert "deferred_2" in output
  374. os.remove('test_defer.tmp')