diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 36210aad..da7c5295 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -52,6 +52,9 @@ "reskip": { "message": "Reskip" }, + "unmute": { + "message": "Unmute" + }, "paused": { "message": "Paused" }, @@ -302,6 +305,9 @@ "skip": { "message": "Skip" }, + "mute": { + "message": "Mute" + }, "skip_category": { "message": "Skip {0}?" }, @@ -604,6 +610,9 @@ "autoSkipOnMusicVideos": { "message": "Auto skip all segments when there is a non-music segment" }, + "muteSegments": { + "message": "Allow segments that mute audio instead of skip" + }, "colorFormatIncorrect": { "message": "Your color is formatted incorrectly. It should be a 3 or 6 digit hex code with a number sign at the beginning." }, diff --git a/public/content.css b/public/content.css index 9d59a22e..13cab8e4 100644 --- a/public/content.css +++ b/public/content.css @@ -472,7 +472,7 @@ input::-webkit-inner-spin-button { text-decoration: underline; } -.sponsorTimeCategories { +.sponsorTimeEditSelector { margin-top: 5px; margin-bottom: 5px; diff --git a/public/options/options.html b/public/options/options.html index 540b5122..b1a0dcb4 100644 --- a/public/options/options.html +++ b/public/options/options.html @@ -50,6 +50,22 @@
+
+ + +
+
+
+
+

diff --git a/src/components/SkipNoticeComponent.tsx b/src/components/SkipNoticeComponent.tsx index 0395c19c..b96ce070 100644 --- a/src/components/SkipNoticeComponent.tsx +++ b/src/components/SkipNoticeComponent.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as CompileConfig from "../../config.json"; import Config from "../config" -import { Category, ContentContainer, CategoryActionType, SponsorHideType, SponsorTime, NoticeVisbilityMode } from "../types"; +import { Category, ContentContainer, CategoryActionType, SponsorHideType, SponsorTime, NoticeVisbilityMode, ActionType } from "../types"; import NoticeComponent from "./NoticeComponent"; import NoticeTextSelectionComponent from "./NoticeTextSectionComponent"; @@ -39,8 +39,9 @@ export interface SkipNoticeState { maxCountdownTime?: () => number; countdownText?: string; - unskipText?: string; - unskipCallback?: (index: number) => void; + skipButtonText?: string; + skipButtonCallback?: (index: number) => void; + showSkipButton?: boolean; downvoting?: boolean; choosingCategory?: boolean; @@ -110,8 +111,9 @@ class SkipNoticeComponent extends React.Component this.unskip(index), + skipButtonText: this.getUnskipText(), + skipButtonCallback: (index) => this.unskip(index), + showSkipButton: true, downvoting: false, choosingCategory: false, @@ -126,7 +128,7 @@ class SkipNoticeComponent extends React.Component 1 + if (this.state.showSkipButton && (this.segments.length > 1 || getCategoryActionType(this.segments[0].category) !== CategoryActionType.POI - || this.props.unskipTime) { + || this.props.unskipTime)) { return ( ); @@ -397,7 +399,7 @@ class SkipNoticeComponent extends React.Component Config.config.skipNoticeDuration, + countdownTime: Config.config.skipNoticeDuration + }; + + // See if the title should be changed + if (!this.autoSkip) { + newState.noticeTitle = chrome.i18n.getMessage("noticeTitle"); + } + + //reset countdown + this.setState(newState, () => { + this.noticeRef.current.resetCountdown(); + }); } /** Sets up notice to be not skipped yet */ @@ -479,36 +503,14 @@ class SkipNoticeComponent extends React.Component this.reskip(index), + skipButtonText: buttonText, + skipButtonCallback: (index) => this.reskip(index), // change max duration to however much of the sponsor is left maxCountdownTime: maxCountdownTime, countdownTime: maxCountdownTime() } as SkipNoticeState; } - reskip(index: number): void { - this.contentContainer().reskipSponsorTime(this.segments[index]); - - const newState: SkipNoticeState = { - unskipText: chrome.i18n.getMessage("unskip"), - unskipCallback: this.unskip.bind(this), - - maxCountdownTime: () => Config.config.skipNoticeDuration, - countdownTime: Config.config.skipNoticeDuration - }; - - // See if the title should be changed - if (!this.autoSkip) { - newState.noticeTitle = chrome.i18n.getMessage("noticeTitle"); - } - - //reset countdown - this.setState(newState, () => { - this.noticeRef.current.resetCountdown(); - }); - } - afterVote(segment: SponsorTime, type: number, category: Category): void { this.addVoteButtonInfo(chrome.i18n.getMessage("voted")); @@ -559,6 +561,52 @@ class SkipNoticeComponent extends React.Component= this.props.segments[0].segment[1]) { + this.setState({ + showSkipButton: false + }); + } + } + + private getUnskipText(): string { + switch (this.props.segments[0].actionType) { + case ActionType.Mute: { + return chrome.i18n.getMessage("unmute"); + } + case ActionType.Skip: + default: { + return chrome.i18n.getMessage("unskip"); + } + } + } + + private getReskipText(): string { + switch (this.props.segments[0].actionType) { + case ActionType.Mute: { + return chrome.i18n.getMessage("mute"); + } + case ActionType.Skip: + default: { + return chrome.i18n.getMessage("reskip"); + } + } + } + + private getSkipText(): string { + switch (this.props.segments[0].actionType) { + case ActionType.Mute: { + return chrome.i18n.getMessage("mute"); + } + case ActionType.Skip: + default: { + return chrome.i18n.getMessage("skip"); + } + } + } } export default SkipNoticeComponent; diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index 8d9f516e..5664f7a9 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -1,12 +1,12 @@ import * as React from "react"; - -import Config from "../config"; import * as CompileConfig from "../../config.json"; - +import Config from "../config"; +import { ActionType, ActionTypes, Category, CategoryActionType, ContentContainer, SponsorTime } from "../types"; import Utils from "../utils"; -import { Category, CategoryActionType, ContentContainer, SponsorTime } from "../types"; -import SubmissionNoticeComponent from "./SubmissionNoticeComponent"; import { getCategoryActionType } from "../utils/categoryUtils"; +import SubmissionNoticeComponent from "./SubmissionNoticeComponent"; + + const utils = new Utils(); export interface SponsorTimeEditProps { @@ -32,6 +32,7 @@ class SponsorTimeEditComponent extends React.Component; + actionTypeOptionRef: React.RefObject; configUpdateListener: () => void; @@ -39,6 +40,7 @@ class SponsorTimeEditComponent extends React.Component this.saveEditTimes()}> + {this.getActionTypeOptions()} + + + ): ""} +
{/* Editing Tools */} @@ -269,6 +284,21 @@ class SponsorTimeEditComponent extends React.Component + {chrome.i18n.getMessage(actionType)} + + ); + } + + return elements; + } + setTimeToNow(index: number): void { this.setTimeTo(index, this.props.contentContainer().getRealCurrentTime()); } @@ -331,6 +361,8 @@ class SponsorTimeEditComponent extends React.Component segment.actionType === ActionType.Mute && segment.segment[0] <= currentTime && segment.segment[1] > currentTime; + return sponsorTimes?.some(checkFunction) || sponsorTimesSubmitting.some(checkFunction); +} + /** * This makes sure the videoID is still correct and if the sponsorTime is included */ function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boolean { const currentVideoID = getYouTubeVideoID(document.URL); - if (currentVideoID !== (videoID || sponsorVideoID) || (sponsorTime && (!sponsorTimes || !sponsorTimes.includes(sponsorTime)) && !sponsorTimesSubmitting.includes(sponsorTime))) { + if (currentVideoID !== (videoID || sponsorVideoID) || (sponsorTime + && (!sponsorTimes || !sponsorTimes?.some((time) => time.segment === sponsorTime.segment)) + && !sponsorTimesSubmitting.some((time) => time.segment === sponsorTime.segment))) { // Something has really gone wrong console.error("[SponsorBlock] The videoID recorded when trying to skip is different than what it should be."); console.error("[SponsorBlock] VideoID recorded: " + sponsorVideoID + ". Actual VideoID: " + currentVideoID); @@ -575,7 +592,7 @@ function setupVideoListeners() { } if (!Config.config.dontShowNotice) { - const currentPoiSegment = sponsorTimes.find((segment) => + const currentPoiSegment = sponsorTimes?.find((segment) => getCategoryActionType(segment.category) === CategoryActionType.POI && video.currentTime - segment.segment[0] > 0 && video.currentTime - segment.segment[0] < previewBar.getMinimumSize(true)); @@ -644,6 +661,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) { const hashPrefix = (await utils.getHash(id, 1)).substr(0, 4); const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, { categories, + actionTypes: Config.config.muteSegments ? [ActionType.Skip, ActionType.Mute] : [ActionType.Skip], userAgent: `${chrome.runtime.id}` }); @@ -945,31 +963,33 @@ async function whitelistCheck() { * Returns info about the next upcoming sponsor skip */ function getNextSkipIndex(currentTime: number, includeIntersectingSegments: boolean, includeNonIntersectingSegments: boolean): - {array: SponsorTime[], index: number, endIndex: number, openNotice: boolean} { + {array: ScheduledTime[], index: number, endIndex: number, openNotice: boolean} { - const sponsorStartTimes = getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments); - const sponsorStartTimesAfterCurrentTime = getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, true, true); + const { includedTimes: submittedArray, startTimeIndexes: sponsorStartTimes } = + getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments); + const { startTimeIndexes: sponsorStartTimesAfterCurrentTime } = getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, true, true); const minSponsorTimeIndex = sponsorStartTimes.indexOf(Math.min(...sponsorStartTimesAfterCurrentTime)); - const endTimeIndex = getLatestEndTimeIndex(sponsorTimes, minSponsorTimeIndex); + const endTimeIndex = getLatestEndTimeIndex(submittedArray, minSponsorTimeIndex); - const unsubmittedSponsorStartTimes = getStartTimes(sponsorTimesSubmitting, includeIntersectingSegments, includeNonIntersectingSegments); - const unsubmittedSponsorStartTimesAfterCurrentTime = getStartTimes(sponsorTimesSubmitting, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, false, false); + const { includedTimes: unsubmittedArray, startTimeIndexes: unsubmittedSponsorStartTimes } = + getStartTimes(sponsorTimesSubmitting, includeIntersectingSegments, includeNonIntersectingSegments); + const { startTimeIndexes: unsubmittedSponsorStartTimesAfterCurrentTime } = getStartTimes(sponsorTimesSubmitting, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, false, false); const minUnsubmittedSponsorTimeIndex = unsubmittedSponsorStartTimes.indexOf(Math.min(...unsubmittedSponsorStartTimesAfterCurrentTime)); - const previewEndTimeIndex = getLatestEndTimeIndex(sponsorTimesSubmitting, minUnsubmittedSponsorTimeIndex); + const previewEndTimeIndex = getLatestEndTimeIndex(unsubmittedArray, minUnsubmittedSponsorTimeIndex); if ((minUnsubmittedSponsorTimeIndex === -1 && minSponsorTimeIndex !== -1) || sponsorStartTimes[minSponsorTimeIndex] < unsubmittedSponsorStartTimes[minUnsubmittedSponsorTimeIndex]) { return { - array: sponsorTimes.filter((segment) => getCategoryActionType(segment.category) === CategoryActionType.Skippable), + array: submittedArray, index: minSponsorTimeIndex, endIndex: endTimeIndex, openNotice: true }; } else { return { - array: sponsorTimesSubmitting.filter((segment) => getCategoryActionType(segment.category) === CategoryActionType.Skippable), + array: unsubmittedArray, index: minUnsubmittedSponsorTimeIndex, endIndex: previewEndTimeIndex, openNotice: false @@ -993,7 +1013,10 @@ function getNextSkipIndex(currentTime: number, includeIntersectingSegments: bool function getLatestEndTimeIndex(sponsorTimes: SponsorTime[], index: number, hideHiddenSponsors = true): number { // Only combine segments for AutoSkip if (index == -1 || - !shouldAutoSkip(sponsorTimes[index])) return index; + !shouldAutoSkip(sponsorTimes[index]) + || sponsorTimes[index].actionType !== ActionType.Skip) { + return index; + } // Default to the normal endTime let latestEndTimeIndex = index; @@ -1004,7 +1027,8 @@ function getLatestEndTimeIndex(sponsorTimes: SponsorTime[], index: number, hideH if (currentSegment[0] <= latestEndTime && currentSegment[1] > latestEndTime && (!hideHiddenSponsors || sponsorTimes[i].hidden === SponsorHideType.Visible) - && shouldAutoSkip(sponsorTimes[i])) { + && shouldAutoSkip(sponsorTimes[i]) + && sponsorTimes[i].actionType === ActionType.Skip) { // Overlapping segment latestEndTimeIndex = i; } @@ -1029,24 +1053,43 @@ function getLatestEndTimeIndex(sponsorTimes: SponsorTime[], index: number, hideH * the current time, but end after */ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments: boolean, includeNonIntersectingSegments: boolean, - minimum?: number, onlySkippableSponsors = false, hideHiddenSponsors = false): number[] { - if (sponsorTimes === null) return []; + minimum?: number, onlySkippableSponsors = false, hideHiddenSponsors = false): {includedTimes: ScheduledTime[], startTimeIndexes: number[]} { + if (!sponsorTimes) return {includedTimes: [], startTimeIndexes: []}; - const startTimes: number[] = []; + const includedTimes: ScheduledTime[] = []; + const startTimeIndexes: number[] = []; - for (let i = 0; i < sponsorTimes?.length; i++) { + const possibleTimes = sponsorTimes.flatMap((sponsorTime) => { + const results = [{ + ...sponsorTime, + scheduledTime: sponsorTime.segment[0] + }] + + if (sponsorTime.actionType === ActionType.Mute) { + // Schedule at the end time to know when to unmute + results.push({ + ...sponsorTime, + scheduledTime: sponsorTime.segment[1] + }) + } + + return results; + }) + + for (let i = 0; i < possibleTimes.length; i++) { if ((minimum === undefined - || ((includeNonIntersectingSegments && sponsorTimes[i].segment[0] >= minimum) - || (includeIntersectingSegments && sponsorTimes[i].segment[0] < minimum && sponsorTimes[i].segment[1] > minimum))) - && (!onlySkippableSponsors || shouldSkip(sponsorTimes[i])) - && (!hideHiddenSponsors || sponsorTimes[i].hidden === SponsorHideType.Visible) - && getCategoryActionType(sponsorTimes[i].category) === CategoryActionType.Skippable) { + || ((includeNonIntersectingSegments && possibleTimes[i].scheduledTime >= minimum) + || (includeIntersectingSegments && possibleTimes[i].scheduledTime < minimum && possibleTimes[i].segment[1] > minimum))) + && (!onlySkippableSponsors || shouldSkip(possibleTimes[i])) + && (!hideHiddenSponsors || possibleTimes[i].hidden === SponsorHideType.Visible) + && getCategoryActionType(possibleTimes[i].category) === CategoryActionType.Skippable) { - startTimes.push(sponsorTimes[i].segment[0]); + startTimeIndexes.push(possibleTimes[i].scheduledTime); + includedTimes.push(possibleTimes[i]); } } - return startTimes; + return { includedTimes, startTimeIndexes }; } /** @@ -1092,13 +1135,27 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u if ((autoSkip || sponsorTimesSubmitting.some((time) => time.segment === skippingSegments[0].segment)) && v.currentTime !== skipTime[1]) { - // Fix for looped videos not working when skipping to the end #426 - // for some reason you also can't skip to 1 second before the end - if (v.loop && v.duration > 1 && skipTime[1] >= v.duration - 1) { - v.currentTime = 0; - } else { - v.currentTime = skipTime[1]; + switch(skippingSegments[0].actionType) { + case ActionType.Skip: { + // Fix for looped videos not working when skipping to the end #426 + // for some reason you also can't skip to 1 second before the end + if (v.loop && v.duration > 1 && skipTime[1] >= v.duration - 1) { + v.currentTime = 0; + } else { + v.currentTime = skipTime[1]; + } + + break; + } + case ActionType.Mute: { + if (!v.muted) { + v.muted = true; + videoMuted = true; + } + break; + } } + } if (!autoSkip @@ -1138,19 +1195,29 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u } function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null) { - //add a tiny bit of time to make sure it is not skipped again - console.log(unskipTime) - video.currentTime = unskipTime ?? segment.segment[0] + 0.001; + if (segment.actionType === ActionType.Mute) { + video.muted = false; + videoMuted = false; + } else { + //add a tiny bit of time to make sure it is not skipped again + video.currentTime = unskipTime ?? segment.segment[0] + 0.001; + } + } function reskipSponsorTime(segment: SponsorTime) { - const skippedTime = Math.max(segment.segment[1] - video.currentTime, 0); - const segmentDuration = segment.segment[1] - segment.segment[0]; - const fullSkip = skippedTime / segmentDuration > manualSkipPercentCount; - - video.currentTime = segment.segment[1]; - sendTelemetryAndCount([segment], skippedTime, fullSkip); - startSponsorSchedule(true, segment.segment[1], false); + if (segment.actionType === ActionType.Mute) { + video.muted = true; + videoMuted = true; + } else { + const skippedTime = Math.max(segment.segment[1] - video.currentTime, 0); + const segmentDuration = segment.segment[1] - segment.segment[0]; + const fullSkip = skippedTime / segmentDuration > manualSkipPercentCount; + + video.currentTime = segment.segment[1]; + sendTelemetryAndCount([segment], skippedTime, fullSkip); + startSponsorSchedule(true, segment.segment[1], false); + } } function createButton(baseID: string, title: string, callback: () => void, imageName: string, isDraggable = false): HTMLElement { @@ -1193,13 +1260,13 @@ function createButton(baseID: string, title: string, callback: () => void, image function shouldAutoSkip(segment: SponsorTime): boolean { return utils.getCategorySelection(segment.category)?.option === CategorySkipOption.AutoSkip || - (Config.config.autoSkipOnMusicVideos && sponsorTimes.some((s) => s.category === "music_offtopic") + (Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic") && getCategoryActionType(segment.category) === CategoryActionType.Skippable); } function shouldSkip(segment: SponsorTime): boolean { return utils.getCategorySelection(segment.category)?.option !== CategorySkipOption.ShowOverlay || - (Config.config.autoSkipOnMusicVideos && sponsorTimes.some((s) => s.category === "music_offtopic")); + (Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic")); } function getControls(): HTMLElement | false { @@ -1333,6 +1400,7 @@ function startOrEndTimingNewSegment() { segment: [getRealCurrentTime()], UUID: null, category: Config.config.defaultCategory, + actionType: ActionType.Skip, source: SponsorSourceType.Local }); } else { @@ -1389,6 +1457,7 @@ function updateSponsorTimesSubmitting(getFromConfig = true) { segment: segmentTime.segment, UUID: segmentTime.UUID, category: segmentTime.category, + actionType: segmentTime.actionType, source: segmentTime.source }); } diff --git a/src/render/SkipNotice.tsx b/src/render/SkipNotice.tsx index 24f98369..5113c2da 100644 --- a/src/render/SkipNotice.tsx +++ b/src/render/SkipNotice.tsx @@ -71,6 +71,10 @@ class SkipNotice { toggleSkip(): void { this.skipNoticeRef?.current?.prepAction(SkipNoticeAction.Unskip); } + + unmutedListener(): void { + this.skipNoticeRef?.current?.unmutedListener(); + } } export default SkipNotice; \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index dd72ad4b..cb5cc0e2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -56,6 +56,13 @@ export enum CategoryActionType { POI = "_POI" } +export enum ActionType { + Skip = "skip", + Mute = "mute" +} + +export const ActionTypes = [ActionType.Skip, ActionType.Mute]; + export type SegmentUUID = string & { __segmentUUIDBrand: unknown }; export type Category = string & { __categoryBrand: unknown }; @@ -69,11 +76,16 @@ export interface SponsorTime { UUID: SegmentUUID; category: Category; + actionType: ActionType; hidden?: SponsorHideType; source?: SponsorSourceType; } +export interface ScheduledTime extends SponsorTime { + scheduledTime: number; +} + export interface PreviewBarOption { color: string, opacity: string diff --git a/test/selenium.test.ts b/test/selenium.test.ts index 530546fa..3ec4037e 100644 --- a/test/selenium.test.ts +++ b/test/selenium.test.ts @@ -13,8 +13,11 @@ test("Selenium Chrome test", async () => { await createSegment(driver, "4", "10.33", "0:04.000 to 0:10.330"); await editSegments(driver, 0, "0:04.000", "0:10.330", "5", "13.211", "0:05.000 to 0:13.211", false); - await autoskipSegment(driver, 5, 13.211); + + await setSegmentActionType(driver, 0, 1, false); + await editSegments(driver, 0, "0:05.000", "0:13.211", "5", "7.5", "0:05.000 to 0:07.500", false); + await muteSkipSegment(driver, 5, 7.5); } finally { await driver.quit(); } @@ -24,7 +27,7 @@ async function setup(): Promise { const options = new Chrome.Options(); options.addArguments("--load-extension=" + Path.join(__dirname, "../dist/")); options.addArguments("--mute-audio"); - options.addArguments("--disable-features=PreloadMediaEngagementData, MediaEngagementBypassAutoplayPolicies") + options.addArguments("--disable-features=PreloadMediaEngagementData, MediaEngagementBypassAutoplayPolicies"); const driver = await new Builder().forBrowser("chrome").setChromeOptions(options).build(); driver.manage().setTimeouts({ @@ -106,6 +109,16 @@ async function editSegments(driver: WebDriver, index: number, expectedStartTimeB await driver.wait(until.elementTextIs(sponsorTimeDisplay, expectedDisplayedTime)); } +async function setSegmentActionType(driver: WebDriver, index: number, actionTypeIndex: number, openSubmitBox: boolean): Promise { + if (openSubmitBox) { + const submitButton = await driver.findElement(By.id("submitButton")); + await submitButton.click(); + } + + const actionTypeSelection = await driver.findElement(By.css(`#sponsorTimeActionTypesSubmissionNotice${index} > option:nth-child(${actionTypeIndex + 1})`)); + actionTypeSelection.click(); +} + async function autoskipSegment(driver: WebDriver, startTime: number, endTime: number): Promise { const video = await driver.findElement(By.css("video")); @@ -113,7 +126,21 @@ async function autoskipSegment(driver: WebDriver, startTime: number, endTime: nu await driver.executeScript("document.querySelector('video').play()"); await driver.sleep(1300); - expect(parseFloat(await video.getAttribute("currentTime"))).toBeGreaterThan(endTime); await driver.executeScript("document.querySelector('video').pause()"); +} + +async function muteSkipSegment(driver: WebDriver, startTime: number, endTime: number): Promise { + const duration = endTime - startTime; + const video = await driver.findElement(By.css("video")); + + await driver.executeScript("document.querySelector('video').currentTime = " + (startTime - 0.5)); + await driver.executeScript("document.querySelector('video').play()"); + + await driver.sleep(1300); + expect(await video.getAttribute("muted")).toEqual("true"); + + await driver.sleep(duration * 1000 + 300); + expect(await video.getAttribute("muted")).toBeNull(); // Default is null for some reason + await driver.executeScript("document.querySelector('video').pause()"); } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8fa7472c..55769505 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,11 @@ "noEmitOnError": false, "typeRoots": [ "node_modules/@types" ], "resolveJsonModule": true, - "jsx": "react" + "jsx": "react", + "lib": [ + "es2019", + "dom", + "dom.iterable" + ] } } \ No newline at end of file