fixtures.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. /* eslint-disable @typescript-eslint/ban-ts-comment */
  2. // Copyright (c) Jupyter Development Team.
  3. // Distributed under the terms of the Modified BSD License.
  4. import { Session, TerminalAPI } from '@jupyterlab/services';
  5. import {
  6. test as base,
  7. Page,
  8. PlaywrightTestArgs,
  9. PlaywrightTestOptions,
  10. PlaywrightWorkerArgs,
  11. PlaywrightWorkerOptions,
  12. TestType
  13. } from '@playwright/test';
  14. import { ContentsHelper } from './contents';
  15. import { galata } from './galata';
  16. import { IJupyterLabPage, IJupyterLabPageFixture } from './jupyterlabpage';
  17. /**
  18. * Galata test arguments
  19. */
  20. export interface IGalataTestArgs extends PlaywrightTestArgs {
  21. /**
  22. * JupyterLab test page.
  23. *
  24. * It brings the following feature on top of Playwright Page object:
  25. * - Goto to JupyterLab URL and wait for the application to be ready
  26. * - Helpers for JupyterLab
  27. * - Settings mock-up
  28. * - State mock-up
  29. * - Track sessions and terminals opened during a test to close them at the end
  30. *
  31. * Note: If autoGoto is true, the filebrowser will be set inside tmpPath.
  32. * Nothing is preventing you to navigate to some other folders.
  33. * So you must avoid creating files outside that directory to avoid
  34. * coupling effects between tests.
  35. *
  36. */
  37. page: IJupyterLabPageFixture;
  38. }
  39. /**
  40. * Galata test configuration
  41. */
  42. export type GalataOptions = {
  43. /**
  44. * Application URL path fragment.
  45. *
  46. * Default: /lab
  47. */
  48. appPath: string;
  49. /**
  50. * Whether to go to JupyterLab page within the fixture or not.
  51. *
  52. * Default: true
  53. */
  54. autoGoto: boolean;
  55. /**
  56. * Galata can keep the uploaded and created files in ``tmpPath`` on
  57. * the server root for debugging purpose. By default the files are
  58. * always deleted
  59. *
  60. * - 'off' - ``tmpPath`` is deleted after each tests
  61. * - 'on' - ``tmpPath`` is never deleted
  62. * - 'only-on-failure' - ``tmpPath`` is deleted except if a test failed or timed out.
  63. */
  64. serverFiles: 'on' | 'off' | 'only-on-failure';
  65. /**
  66. * Mock JupyterLab state in-memory or not.
  67. *
  68. * Possible values are:
  69. * - true (default): JupyterLab state will be mocked on a per test basis
  70. * - false: JupyterLab state won't be mocked (Be careful it will write state in local files)
  71. * - Record<string, unknown>: Initial JupyterLab data state - Mapping (state key, value).
  72. *
  73. * By default the state is stored in-memory.
  74. */
  75. mockState: boolean | Record<string, unknown>;
  76. /**
  77. * Mock JupyterLab settings in-memory or not.
  78. *
  79. * Possible values are:
  80. * - true: JupyterLab settings will be mocked on a per test basis
  81. * - false: JupyterLab settings won't be mocked (Be careful it will read & write settings local files)
  82. * - Record<string, unknown>: Mapping {pluginId: settings} that will be default user settings
  83. *
  84. * The default value is `galata.DEFAULT_SETTINGS`
  85. *
  86. * By default the settings are stored in-memory. However the
  87. * they are still initialized with the hard drive values.
  88. */
  89. mockSettings: boolean | Record<string, unknown>;
  90. /**
  91. * Sessions created during the test.
  92. *
  93. * Possible values are:
  94. * - null: The sessions API won't be mocked
  95. * - Map<string, Session.IModel>: The sessions created during a test.
  96. *
  97. * By default the sessions created during a test will be tracked and disposed at the end.
  98. */
  99. sessions: Map<string, Session.IModel> | null;
  100. /**
  101. * Terminals created during the test.
  102. *
  103. * Possible values are:
  104. * - null: The Terminals API won't be mocked
  105. * - Map<string, TerminalsAPI.IModel>: The Terminals created during a test.
  106. *
  107. * By default the Terminals created during a test will be tracked and disposed at the end.
  108. */
  109. terminals: Map<string, TerminalAPI.IModel> | null;
  110. /**
  111. * Unique test temporary path created on the server.
  112. *
  113. * Note: if you override this string, you will need to take care of creating the
  114. * folder and cleaning it.
  115. */
  116. tmpPath: string;
  117. /**
  118. * Wait for the application page to be ready
  119. *
  120. * @param page Playwright Page model
  121. * @param helpers JupyterLab helpers
  122. */
  123. waitForApplication: (page: Page, helpers: IJupyterLabPage) => Promise<void>;
  124. };
  125. /**
  126. * JupyterLab customized test.
  127. */
  128. // @ts-ignore
  129. export const test: TestType<
  130. IGalataTestArgs & GalataOptions & PlaywrightTestOptions,
  131. PlaywrightWorkerArgs & PlaywrightWorkerOptions
  132. > = base.extend<GalataOptions>({
  133. /**
  134. * `baseURL` used for all pages in the test. Takes priority over `contextOptions`.
  135. * @see BrowserContextOptions
  136. *
  137. * It can also be set with `TARGET_URL` environment variable and default to `http://localhost:8888`.
  138. */
  139. baseURL: async ({ baseURL }, use) => {
  140. await use(baseURL ?? process.env.TARGET_URL ?? 'http://localhost:8888');
  141. },
  142. /**
  143. * Application URL path fragment.
  144. *
  145. * Default: /lab
  146. */
  147. appPath: '/lab',
  148. /**
  149. * Whether to go to JupyterLab page within the fixture or not.
  150. *
  151. * Default: true.
  152. *
  153. * Note: Setting it to false allows to register new route mock-ups for example.
  154. */
  155. autoGoto: true,
  156. /**
  157. * Mock JupyterLab state in-memory or not.
  158. *
  159. * Possible values are:
  160. * - true (default): JupyterLab state will be mocked on a per test basis
  161. * - false: JupyterLab state won't be mocked (Be careful it will write state in local files)
  162. * - Record<string, unknown>: Initial JupyterLab data state - Mapping (state key, value).
  163. *
  164. * By default the state is stored in-memory
  165. */
  166. mockState: true,
  167. /**
  168. * Mock JupyterLab settings in-memory or not.
  169. *
  170. * Possible values are:
  171. * - true: JupyterLab settings will be mocked on a per test basis
  172. * - false: JupyterLab settings won't be mocked (Be careful it may write settings local files)
  173. * - Record<string, unknown>: Mapping {pluginId: settings} that will be default user settings
  174. *
  175. * The default value is `galata.DEFAULT_SETTINGS`
  176. *
  177. * By default the settings are stored in-memory. However the
  178. * they are still initialized with the hard drive values.
  179. */
  180. mockSettings: galata.DEFAULT_SETTINGS,
  181. /**
  182. * Galata can keep the uploaded and created files in ``tmpPath`` on
  183. * the server root for debugging purpose. By default the files are
  184. * always deleted.
  185. *
  186. * - 'off' - ``tmpPath`` is deleted after each tests
  187. * - 'on' - ``tmpPath`` is never deleted
  188. * - 'only-on-failure' - ``tmpPath`` is deleted except if a test failed or timed out.
  189. */
  190. serverFiles: 'off',
  191. /**
  192. * Sessions created during the test.
  193. *
  194. * Possible values are:
  195. * - null: The sessions API won't be mocked
  196. * - Map<string, Session.IModel>: The sessions created during a test.
  197. *
  198. * By default the sessions created during a test will be tracked and disposed at the end.
  199. */
  200. sessions: async ({ baseURL }, use) => {
  201. const sessions = new Map<string, Session.IModel>();
  202. await use(sessions);
  203. if (sessions.size > 0) {
  204. await galata.Mock.clearRunners(
  205. baseURL!,
  206. [...sessions.keys()],
  207. 'sessions'
  208. );
  209. }
  210. },
  211. /**
  212. * Terminals created during the test.
  213. *
  214. * Possible values are:
  215. * - null: The Terminals API won't be mocked
  216. * - Map<string, TerminalsAPI.IModel>: The Terminals created during a test.
  217. *
  218. * By default the Terminals created during a test will be tracked and disposed at the end.
  219. */
  220. terminals: async ({ baseURL }, use) => {
  221. const terminals = new Map<string, TerminalAPI.IModel>();
  222. await use(terminals);
  223. if (terminals.size > 0) {
  224. await galata.Mock.clearRunners(
  225. baseURL!,
  226. [...terminals.keys()],
  227. 'terminals'
  228. );
  229. }
  230. },
  231. /**
  232. * Unique test temporary path created on the server.
  233. *
  234. * Note: if you override this string, you will need to take care of creating the
  235. * folder and cleaning it.
  236. */
  237. tmpPath: async ({ baseURL, serverFiles }, use, testInfo) => {
  238. const parts = testInfo.outputDir.split('/');
  239. // Remove appended retry part for reproducibility
  240. const testFolder = parts[parts.length - 1].replace(/-retry\d+$/i, '');
  241. const contents = new ContentsHelper(baseURL!);
  242. if (await contents.directoryExists(testFolder)) {
  243. await contents.deleteDirectory(testFolder);
  244. }
  245. // Create the test folder on the server
  246. await contents.createDirectory(testFolder);
  247. await use(testFolder);
  248. // Delete the test folder on the server
  249. // If serverFiles is 'on' or 'only-on-failure', keep the server files for the test
  250. if (
  251. serverFiles === 'off' ||
  252. (serverFiles === 'only-on-failure' &&
  253. (testInfo.status === 'passed' || testInfo.status === 'skipped'))
  254. ) {
  255. await contents.deleteDirectory(testFolder);
  256. }
  257. },
  258. /**
  259. * Wait for the application page to be ready
  260. *
  261. * @param page Playwright Page model
  262. * @param helpers JupyterLab helpers
  263. */
  264. waitForApplication: async ({ baseURL }, use, testInfo) => {
  265. const waitIsReady = async (
  266. page: Page,
  267. helpers: IJupyterLabPage
  268. ): Promise<void> => {
  269. await page.waitForSelector('#jupyterlab-splash', {
  270. state: 'detached'
  271. });
  272. await helpers.waitForCondition(() => {
  273. return helpers.activity.isTabActive('Launcher');
  274. });
  275. // Oddly current tab is not always set to active
  276. if (!(await helpers.isInSimpleMode())) {
  277. await helpers.activity.activateTab('Launcher');
  278. }
  279. };
  280. await use(waitIsReady);
  281. },
  282. /**
  283. * JupyterLab test page.
  284. *
  285. * It brings the following feature on top of Playwright Page object:
  286. * - Goto to JupyterLab URL and wait for the application to be ready (autoGoto == true)
  287. * - Helpers for JupyterLab
  288. * - Settings mock-up
  289. * - State mock-up
  290. * - Track sessions and terminals opened during a test to close them at the end
  291. *
  292. * Note: If autoGoto is true, the filebrowser will be set inside tmpPath.
  293. * Nothing is preventing you to navigate to some other folders.
  294. * So you must avoid creating files outside that directory to avoid
  295. * coupling effects between tests.
  296. */
  297. // @ts-ignore
  298. page: async (
  299. {
  300. appPath,
  301. autoGoto,
  302. baseURL,
  303. mockSettings,
  304. mockState,
  305. page,
  306. sessions,
  307. terminals,
  308. tmpPath,
  309. waitForApplication
  310. },
  311. use
  312. ) => {
  313. await use(
  314. await galata.initTestPage(
  315. appPath,
  316. autoGoto,
  317. baseURL!,
  318. mockSettings,
  319. mockState,
  320. page,
  321. sessions,
  322. terminals,
  323. tmpPath,
  324. waitForApplication
  325. )
  326. );
  327. }
  328. });