AtAudioView.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. const { I } = inject();
  2. const assert = require("assert");
  3. const Helpers = require("../tests/helpers");
  4. module.exports = {
  5. _stageSelector: "#waveform-layer-main",
  6. _progressBarSelector: "loading-progress-bar",
  7. _controlMenuSelector: ".lsf-audio-control",
  8. _settingsMenuSelector: ".lsf-audio-config",
  9. _volumeSliderSelector: ".lsf-audio-slider__range",
  10. _volumeInputSelector: ".lsf-audio-slider__input",
  11. _muteButtonSelector: ".lsf-audio-control__mute-button",
  12. _playbackSpeedSliderSelector:
  13. ".lsf-audio-config__modal > .lsf-audio-config__scroll-content > .lsf-audio-slider:nth-child(2) .lsf-audio-slider__range",
  14. _playbackSpeedInputSelector:
  15. ".lsf-audio-config__modal > .lsf-audio-config__scroll-content > .lsf-audio-slider:nth-child(2) .lsf-audio-slider__input",
  16. _amplitudeSliderSelector:
  17. ".lsf-audio-config__modal > .lsf-audio-config__scroll-content > .lsf-audio-slider:nth-child(3) .lsf-audio-slider__range",
  18. _amplitudeInputSelector:
  19. ".lsf-audio-config__modal > .lsf-audio-config__scroll-content > .lsf-audio-slider:nth-child(3) .lsf-audio-slider__input",
  20. _hideTimelineButtonSelector: ".lsf-audio-config__buttons > .lsf-audio-config__menu-button:nth-child(1)",
  21. _hideWaveformButtonSelector: ".lsf-audio-config__buttons > .lsf-audio-config__menu-button:nth-child(2)",
  22. _audioElementSelector: '[data-testid="waveform-audio"]',
  23. _seekBackwardButtonSelector: "button[aria-label='Seek backward']",
  24. _playButtonSelector: "button[aria-label='Play']",
  25. _seekForwardButtonSelector: "button[aria-label='Seek forward']",
  26. _errorSelector: '[data-testid="error:audio"]',
  27. _httpErrorSelector: '[data-testid="error:http"]',
  28. _stageBbox: { x: 0, y: 0, width: 0, height: 0 },
  29. async lookForStage() {
  30. I.scrollPageToTop();
  31. const bbox = await I.grabElementBoundingRect(this._stageSelector);
  32. this._stageBbox = bbox;
  33. },
  34. async waitForAudio() {
  35. await I.executeScript(Helpers.waitForAudio);
  36. I.waitForInvisible(this._progressBarSelector, 30);
  37. I.waitForDetached("loading-progress-bar", 30);
  38. await I.executeScript(Helpers.waitForAudioCanvases);
  39. I.waitTicks(2);
  40. },
  41. getCurrentAudio() {
  42. return I.executeScript(Helpers.getCurrentMedia, "audio");
  43. },
  44. /**
  45. * Mousedown - mousemove - mouseup drawing on the AudioView. Works in couple of lookForStage.
  46. * @example
  47. * await AtAudioView.lookForStage();
  48. * AtAudioView.dragAudioElement(50, 200);
  49. * @param x {number}
  50. * @param shiftX {number}
  51. */
  52. dragAudioElement(x, shiftX, shouldRelease = true) {
  53. I.scrollPageToTop();
  54. I.moveMouse(this._stageBbox.x + x, this._stageBbox.y + this._stageBbox.height / 2);
  55. I.pressMouseDown();
  56. I.moveMouse(this._stageBbox.x + x + shiftX, this._stageBbox.y + this._stageBbox.height / 2, 3);
  57. if (shouldRelease === false) return;
  58. I.pressMouseUp();
  59. I.waitTicks(3);
  60. },
  61. /**
  62. * Click on the Audio waveform at the given x coordinate, and optional y coordinate.
  63. * @example
  64. * await AtAudioView.lookForStage();
  65. * AtAudioView.clickAt(50);
  66. * @param {number} x
  67. * @param {number} [y=undefined] - if not provided, will click at the center of the waveform
  68. */
  69. clickAt(x, y) {
  70. y = y !== undefined ? y : this._stageBbox.height / 2;
  71. I.scrollPageToTop();
  72. I.clickAt(this._stageBbox.x + x, this._stageBbox.y + y);
  73. I.waitTicks(3); // We gotta wait here because clicks on the canvas are not processed immediately
  74. },
  75. clickAtBeginning() {
  76. this.clickAt(0);
  77. },
  78. clickAtEnd() {
  79. // Clicking on the end of the canvas doesn't quite work, so we click a bit before the end
  80. // to make sure we're it is not clicking outside the canvas, and move the cursor over.
  81. this.clickAt(this._stageBbox.width - 1);
  82. this.dragAudioElement(this._stageBbox.width - 1, 1);
  83. },
  84. async createRegion(tagName, start, length) {
  85. const { x, y, height } = await this.getWrapperPosition(tagName);
  86. return I.dragAndDropMouse(
  87. {
  88. x: x + start,
  89. y: y + height / 2,
  90. },
  91. {
  92. x: x + start + length,
  93. y: y + height / 2,
  94. },
  95. );
  96. },
  97. async getWrapperPosition(tagName) {
  98. const wrapperPosition = await I.executeScript((tagName) => {
  99. const _ws = Htx.annotationStore.selected.names.get(tagName)._ws;
  100. // `visualizer.wrapper` is for Audio v3
  101. const wrapper = _ws.visualizer?.wrapper ?? _ws.container;
  102. const bbox = wrapper.getBoundingClientRect();
  103. return {
  104. x: bbox.x,
  105. y: bbox.y,
  106. width: bbox.width,
  107. height: bbox.height,
  108. };
  109. }, tagName);
  110. return wrapperPosition;
  111. },
  112. async moveRegion(regionId, offset = 30) {
  113. const regionPosition = await I.executeScript(
  114. ({ regionId, stageBbox }) => {
  115. const region = Htx.annotationStore.selected.regions.find((r) => r.cleanId === regionId);
  116. const wsRegion = region._ws_region;
  117. if (!wsRegion.inViewport) {
  118. return null;
  119. }
  120. const { height } = wsRegion.visualizer;
  121. return {
  122. x: (wsRegion.xStart + wsRegion.xEnd) / 2 + stageBbox.x,
  123. y: height / 2 + stageBbox.y,
  124. };
  125. },
  126. { regionId, stageBbox: this._stageBbox },
  127. );
  128. if (!regionPosition) return;
  129. return I.dragAndDropMouse(regionPosition, {
  130. x: regionPosition.x + offset,
  131. y: regionPosition.y,
  132. });
  133. },
  134. /**
  135. * Toggle the audio control menu. This houses the volume slider and mute button.
  136. */
  137. toggleControlsMenu() {
  138. I.click(this._controlMenuSelector);
  139. },
  140. /**
  141. * Toggle the audio settings menu. This houses the playback speed slider, amplitude slider, and interface visibility toggles.
  142. */
  143. toggleSettingsMenu() {
  144. I.click(this._settingsMenuSelector);
  145. },
  146. /**
  147. * Zooms in the Audio Waveform by using the mouse wheel at the given relative position.
  148. * @param {number} deltaY
  149. * @param {Object} [atPoint] - Point where the wheel action will be called
  150. * @param {number} [atPoint.x=0.5] - relative X coordinate
  151. * @param {number} [atPoint.y=0.5] - relative Y coordinate
  152. * @returns {Promise<void>}
  153. *
  154. * @example
  155. * // zoom in
  156. * await AtAudioView.zoomToPoint(-100, { x: .01 });
  157. * // zoom out
  158. * await AtAudioView.zoomToPoint(100);
  159. */
  160. async zoomToPoint(deltaY, atPoint = { x: 0.5, y: 0.5 }) {
  161. const { x = 0.5, y = 0.5 } = atPoint;
  162. I.scrollPageToTop();
  163. const stageBBox = await I.grabElementBoundingRect(this._stageSelector);
  164. I.clickAt(stageBBox.x + stageBBox.width * x, stageBBox.y + stageBBox.height * y); // click to focus the canvas
  165. I.pressKeyDown("CommandOrControl");
  166. I.mouseWheel({ deltaY });
  167. I.pressKeyUp("CommandOrControl");
  168. },
  169. /**
  170. * Asserts the current volume of the audio player the slider, input and the audio player.
  171. * @param {number} value - volume in the range [0, 100]
  172. * @returns {Promise<void>}
  173. *
  174. * @example
  175. * await AtAudioView.seeVolume(50);
  176. */
  177. async seeVolume(value) {
  178. this.toggleControlsMenu();
  179. I.seeInField(this._volumeInputSelector, value);
  180. I.seeInField(this._volumeSliderSelector, value);
  181. const volume = await I.grabAttributeFrom(this._audioElementSelector, "volume");
  182. const muted = await I.grabAttributeFrom(this._audioElementSelector, "muted");
  183. if (muted) {
  184. assert.equal(volume, null, "Volume doesn't match in audio element");
  185. } else {
  186. assert.equal(volume, value / 100, "Volume doesn't match in audio element");
  187. }
  188. this.toggleControlsMenu();
  189. },
  190. /**
  191. * Sets the volume of the audio player via the input.
  192. * @param {number} value - volume in the range [0, 100]
  193. *
  194. * @example
  195. * AtAudioView.setVolumeInput(50);
  196. */
  197. setVolumeInput(value) {
  198. this.toggleControlsMenu();
  199. I.clearField(this._volumeInputSelector);
  200. I.fillField(this._volumeInputSelector, value);
  201. I.seeInField(this._volumeInputSelector, value);
  202. this.toggleControlsMenu();
  203. },
  204. /**
  205. * Asserts the current playback speed of the audio player the slider, input and the audio player.
  206. * @param {number} value - speed in the range [0.5, 2.5]
  207. * @returns {Promise<void>}
  208. *
  209. * @example
  210. * await AtAudioView.seePlaybackSpeed(1.5);
  211. */
  212. async seePlaybackSpeed(value) {
  213. this.toggleSettingsMenu();
  214. I.seeInField(this._playbackSpeedInputSelector, value);
  215. I.seeInField(this._playbackSpeedSliderSelector, value);
  216. const playbackSpeed = await I.grabAttributeFrom(this._audioElementSelector, "playbackRate");
  217. assert.equal(playbackSpeed, value, "Playback speed doesn't match in audio element");
  218. this.toggleSettingsMenu();
  219. },
  220. /**
  221. * Sets the playback speed of the audio player via the input.
  222. * @param {number} value - speed in the range [0.5, 2.5]
  223. *
  224. * @example
  225. * AtAudioView.setPlaybackSpeedInput(1.5);
  226. */
  227. setPlaybackSpeedInput(value) {
  228. this.toggleSettingsMenu();
  229. // it was not easy to set this field, so we have to carefully remove value and put it
  230. I.doubleClick(locate(this._playbackSpeedInputSelector));
  231. I.pressKey("Backspace");
  232. I.fillField(this._playbackSpeedInputSelector, value);
  233. this.toggleSettingsMenu();
  234. },
  235. /**
  236. * Asserts the current amplitude of the audio player the slider, and the input.
  237. * @param {number} value - amplitude (y-axis zoom) in the range [1, 150]
  238. * @returns {Promise<void>}
  239. *
  240. * @example
  241. * await AtAudioView.seeAmplitude(10);
  242. */
  243. async seeAmplitude(value) {
  244. this.toggleSettingsMenu();
  245. I.seeInField(this._amplitudeInputSelector, value);
  246. I.seeInField(this._amplitudeSliderSelector, value);
  247. this.toggleSettingsMenu();
  248. },
  249. /**
  250. * Sets the amplitude of the audio player via the input.
  251. * @param {number} value - speed in the range [1, 150]
  252. *
  253. * @example
  254. * AtAudioView.setAmplitudeInput(10);
  255. */
  256. setAmplitudeInput(value) {
  257. this.toggleSettingsMenu();
  258. I.clearField(this._amplitudeInputSelector);
  259. I.fillField(this._amplitudeInputSelector, value);
  260. this.toggleSettingsMenu();
  261. },
  262. clickMuteButton() {
  263. this.toggleControlsMenu();
  264. I.click(this._muteButtonSelector);
  265. this.toggleControlsMenu();
  266. },
  267. clickPlayButton() {
  268. I.click(this._playButtonSelector);
  269. },
  270. clickPauseButton() {
  271. I.click(this._playButtonSelector);
  272. },
  273. async seeErrorHandler(value, selector = null) {
  274. selector = selector ? this[selector] : this._errorSelector;
  275. I.seeElement(selector);
  276. const error = await I.grabTextFrom(selector);
  277. const matcher = new RegExp(value);
  278. assert.match(error, matcher);
  279. },
  280. /**
  281. * Asserts whether the audio player is reporting as paused.
  282. * @returns {Promise<void>}
  283. */
  284. async seeIsPlaying(playing, timeout = 5) {
  285. await I.waitForFunction(
  286. ([selector, expectedPlaying]) => {
  287. const audioElement = document.querySelector(selector);
  288. if (!audioElement) return false;
  289. const isPlaying = !audioElement.paused;
  290. console.log("!> waitForFunction", isPlaying === expectedPlaying, expectedPlaying, isPlaying);
  291. return isPlaying === expectedPlaying;
  292. },
  293. [this._audioElementSelector, playing],
  294. timeout,
  295. );
  296. },
  297. };