Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/api/class-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ This object represents a single parsed Ad
- `creatives: Array<Object>` [go to object](#creative)
- `extensions: Array<Object>` [go to object](#extension)
- `adVerifications: Array<Object>` [go to object](#ad-verification)
- `hasFailed: Boolean` Indicates that the parsed node has failed to be unwrapped or parsed. Only available with the option keepFailedAdPod on ads with a sequence.

## Creative<a name="creative"></a>

Expand Down Expand Up @@ -229,4 +230,4 @@ This object represents a generic Creative. It's used as a parent object for more

- `url: String|null`,
- `width: String|null`,
- `height: String|null`
- `height: String|null`
179 changes: 179 additions & 0 deletions spec/vast_parser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -757,4 +757,183 @@ describe('VASTParser', () => {
expect(VastParser.getEstimatedBitrate()).toEqual(42);
});
});

describe('keepFailedAdPod option', () => {
describe('when keepFailedAdPod is false (default)', () => {
it('should remove ads with errors from the response', async () => {
fetcher.setOptions({ urlHandler: nodeUrlHandler });
VastParser = new VASTParser({ fetcher });

const parser = new DOMParser();
const adWithSequence = parser.parseFromString(
`<VAST version="4.3">
<Ad sequence="1">
<InLine>
<AdSystem>Test</AdSystem>
<AdTitle>Failed Ad</AdTitle>
<Creatives>
</Creatives>
</InLine>
</Ad>
</VAST>`,
'text/xml'
);

const response = await VastParser.parseVAST(adWithSequence, {
keepFailedAdPod: false
})
Comment thread
clementFrancon marked this conversation as resolved.
Outdated

expect(response.ads.length).toBe(0);
});

it('should remove ads that failed to unwrap', async () => {
fetcher.setOptions({ urlHandler: nodeUrlHandler });
VastParser = new VASTParser({ fetcher });

const wrapperFailXml = await nodeUrlHandler.get(
'./spec/samples/wrapper-empty-no-creative.xml'
);

const response = await VastParser.parseVAST(wrapperFailXml.xml);

expect(response.ads.length).toBe(0);
});
});

describe('when keepFailedAdPod is true', () => {
it('should keep ads with errors that have a sequence (ad pod)', async () => {
fetcher.setOptions({ urlHandler: nodeUrlHandler });
VastParser = new VASTParser({ fetcher });

const parser = new DOMParser();
const adPodWithFailure = parser.parseFromString(
`<VAST version="4.3">
<Ad sequence="1">
<InLine>
<AdSystem>Test</AdSystem>
<AdTitle>Failed Ad in Pod</AdTitle>
<Creatives>
</Creatives>
</InLine>
</Ad>
</VAST>`,
'text/xml'
);

const response = await VastParser.parseVAST(adPodWithFailure, {
keepFailedAdPod: true
});

expect(response.ads.length).toBe(1);
const ad = response.ads[0];
expect(ad.hasFailed).toBe(true);
expect(ad.sequence).toBe('1');
const hasValidCreatives = ad.creatives.some(
(creative) =>
creative.mediaFiles?.length > 0 || creative.variations?.length > 0
);
expect(hasValidCreatives).toBe(false);
});

it('should remove standalone ads without sequence even when keepFailedAdPod is true', async () => {
fetcher.setOptions({ urlHandler: nodeUrlHandler });
VastParser = new VASTParser({ fetcher });

const parser = new DOMParser();
const standaloneFailedAd = parser.parseFromString(
`<VAST version="4.3">
<Ad>
<InLine>
<AdSystem>Test</AdSystem>
<AdTitle>Failed Standalone Ad</AdTitle>
<Creatives>
</Creatives>
</InLine>
</Ad>
</VAST>`,
'text/xml'
);

const response = await VastParser.parseVAST(standaloneFailedAd, {
keepFailedAdPod: true
});

expect(response.ads.length).toBe(0);
});

it('should maintain ad pod sequence with failed ads', async () => {
const parser = new DOMParser();
const adPodWithFailureXml = parser.parseFromString(
`<VAST version="4.3">
<Ad sequence="1">
<InLine>
<AdSystem>Test</AdSystem>
<AdTitle>Ad 1</AdTitle>
<Creatives>
<Creative>
<Linear>
<Duration>00:00:15</Duration>
<MediaFiles>
<MediaFile delivery="progressive" type="video/mp4" width="1280" height="720">
<![CDATA[http://example.com/video.mp4]]>
</MediaFile>
</MediaFiles>
</Linear>
</Creative>
</Creatives>
</InLine>
</Ad>
<Ad sequence="2">
<InLine>
<AdSystem>Test</AdSystem>
<AdTitle>Ad 2 - Failed</AdTitle>
<Creatives>
</Creatives>
</InLine>
</Ad>
<Ad sequence="3">
<InLine>
<AdSystem>Test</AdSystem>
<AdTitle>Ad 3</AdTitle>
<Creatives>
<Creative>
<Linear>
<Duration>00:00:15</Duration>
<MediaFiles>
<MediaFile delivery="progressive" type="video/mp4" width="1280" height="720">
<![CDATA[http://example.com/video2.mp4]]>
</MediaFile>
</MediaFiles>
</Linear>
</Creative>
</Creatives>
</InLine>
</Ad>
</VAST>`,
'text/xml'
);

fetcher.setOptions({ urlHandler: nodeUrlHandler });
VastParser = new VASTParser({ fetcher });

const responseWithKeepFailed = await VastParser.parseVAST(
adPodWithFailureXml,
{ keepFailedAdPod: true }
);

expect(responseWithKeepFailed.ads.length).toBe(3);
expect(responseWithKeepFailed.ads[0].sequence).toBe('1');
expect(responseWithKeepFailed.ads[1].sequence).toBe('2');
expect(responseWithKeepFailed.ads[1].hasFailed).toBe(true);
expect(responseWithKeepFailed.ads[2].sequence).toBe('3');

const responseWithoutKeepFailed = await VastParser.parseVAST(
adPodWithFailureXml,
{ keepFailedAdPod: false }
);

expect(responseWithoutKeepFailed.ads.length).toBe(2);
});
});
});
});
13 changes: 12 additions & 1 deletion src/parser/vast_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class VASTParser extends EventEmitter {
this.remainingAds = [];
this.parsingOptions = {};
this.fetcher = fetcher || null;
this.keepFailedAdPod = false;
}

/**
Expand Down Expand Up @@ -72,6 +73,7 @@ export class VASTParser extends EventEmitter {
initParsingStatus(options = {}) {
this.maxWrapperDepth = options.wrapperLimit || DEFAULT_MAX_WRAPPER_DEPTH;
this.parsingOptions = { allowMultipleAds: options.allowMultipleAds };
this.keepFailedAdPod = options.keepFailedAdPod || false;
this.rootURL = '';
this.resetParsingStatus();
updateEstimatedBitrate(options.byteLength, options.requestDuration);
Expand Down Expand Up @@ -465,7 +467,16 @@ export class VASTParser extends EventEmitter {
{ extensions: ad.extensions },
{ system: ad.system }
);
vastResponse.ads.splice(index, 1);

// Only remove failed ads if keepFailedAdPod is not enabled
// This is useful for ad pods where failed ads should remain in the response
// to maintain sequence structure and enable fallback mechanisms
if (this.keepFailedAdPod && ad.sequence) {
ad.hasFailed = true;
}
else {
vastResponse.ads.splice(index, 1);
}
}
}
}
Expand Down