// import { promises as fs } from 'fs' import fs from 'fs-extra' import test from 'ava' import parser from 'fast-xml-parser' import axios from 'axios' import { structuredDataTest } from 'structured-data-testing-tool' import { Google, Twitter } from 'structured-data-testing-tool/presets' require('dotenv').config() function isString( maybeString ) { return (typeof maybeString === 'string' || maybeString instanceof String) } async function pageContains ( needle, pageUrlString ) { const pageUrlInstance = new URL( pageUrlString ) const pagePath = `./dist${ pageUrlInstance.pathname }/index.html` const pageHtml = await fs.readFile( pagePath , 'utf-8' ) return pageHtml.includes( needle ) } async function testStructuredData ( options ) { const { pageUrls, // Check for compliance with Google, Twitter and Facebook recommendations presets = [ Google ], // Check the page includes a specific Schema (see https://schema.org/docs/full.html for a list) schemas } = options for ( const url of pageUrls ) { const pagePath = `./dist${ url.pathname }/index.html` const pageHtml = await fs.readFile( pagePath , 'utf-8' ) // https://github.com/glitchdigital/structured-data-testing-tool#api await structuredDataTest( pageHtml , { presets, schemas }).then(res => { return res }).catch(err => { // console.log( 'err.res.failed', err.res.failed ) if (err.type === 'VALIDATION_FAILED') { // t.fail( 'Some structured data tests failed.' ) const validationError = new Error( 'Some structured data tests failed.' ) validationError.failed = err.res.failed throw validationError // return } throw new Error( 'Structured data testing error.', err ) }) // console.log('result', tvUrl.pathname, Object.keys( result )) } } test.before(async t => { const sitemapXml = await fs.readFile('./dist/sitemap.xml', 'utf-8') const sitemap = parser.parse( sitemapXml ) // Store sitemap urls to context t.context.sitemapUrls = sitemap.urlset.url.map( tag => new URL( tag.loc ) ) }) test('Sitemap contains no double slashes in paths', (t) => { // console.log('t.context.sitemapUrls', t.context.sitemapUrls) const urlsWithDoubleSlashes = t.context.sitemapUrls.filter( url => url.pathname.includes('//') ) if ( urlsWithDoubleSlashes.length > 0) { t.fail( `${ urlsWithDoubleSlashes.length } urls with doubles slashes found including ${ urlsWithDoubleSlashes[0] }` ) } t.log( `${t.context.sitemapUrls.length} valid sitemap listings` ) t.pass() }) test('Sitemap mostly matches production', async (t) => { // console.log('t.context.sitemapUrls', t.context.sitemapUrls) const theshold = 10 const urlsNotOnLive = new Set() // const newLocalUrls = new Set() const liveSitemapXml = await axios( 'https://doesitarm.com/sitemap.xml' ).then( response => response.data ) const liveSitemap = parser.parse( liveSitemapXml ) // Store sitemap urls to context const liveSitemapUrls = new Map( liveSitemap.urlset.url.map( tag => [ tag.loc, new URL( tag.loc )] ) ) for ( const localUrl of t.context.sitemapUrls ) { const theoreticalLiveUrl = `https://doesitarm.com${ localUrl.pathname }` if ( liveSitemapUrls.has( theoreticalLiveUrl ) ) { liveSitemapUrls.delete( theoreticalLiveUrl ) continue } // localUrl is either: Missing or New urlsNotOnLive.add( theoreticalLiveUrl ) } const message = `${ urlsNotOnLive.size } new or missing from live and ${ liveSitemapUrls.size } not found locally` const totalDifferences = urlsNotOnLive.size + liveSitemapUrls.size const liveSitemapUrlStrings = new Set( liveSitemapUrls.keys() ) if ( totalDifferences >= 0 ) { t.log( 'Missing from live', urlsNotOnLive ) t.log( 'Not found locally', liveSitemapUrlStrings ) } if ( totalDifferences >= theshold ) { t.fail( message ) } t.log( message ) t.pass() }) test('All Category pages have valid FAQPage structured data', async (t) => { const categoryUrls = t.context.sitemapUrls.filter( url => url.pathname.startsWith('/kind/') ) try { await testStructuredData({ pageUrls: categoryUrls, schemas: [ 'FAQPage' ], presets: [ Google, // Twitter ], }) } catch ( error ) { console.log('failed', error.failed) t.fail( error.message ) } t.log( `${categoryUrls.length} valid pages` ) t.pass() }) test('All Device pages have valid FAQPage structured data', async (t) => { const deviceUrls = t.context.sitemapUrls.filter( url => url.pathname.startsWith('/device/') ) try { await testStructuredData({ pageUrls: deviceUrls, schemas: [ 'FAQPage' ], presets: [ Google, // Twitter ], }) } catch ( error ) { console.log('failed', error.failed) t.fail( error.message ) } t.log( `${deviceUrls.length} valid pages` ) t.pass() }) test('All TV pages have valid VideoObject structured data', async (t) => { const tvUrls = t.context.sitemapUrls.filter( url => url.pathname.startsWith('/tv/') ) try { await testStructuredData({ pageUrls: tvUrls, schemas: [ 'VideoObject' ] }) } catch ( error ) { console.log('failed', error.failed) t.fail( error.message ) } t.log( `${tvUrls.length} valid pages` ) t.pass() }) test('All App pages with bundle data have bundle data visuals', async (t) => { const appUrls = t.context.sitemapUrls.filter( url => url.pathname.startsWith('/app/') ) const appsWithBundleIds = await fs.readJson('./static/app-list.json', 'utf-8').then( appList => { return appList.filter( app => { return app.bundleIds.length > 0 }) }) t.log(`${appsWithBundleIds.length} apps with bundle IDs`) try { for ( const app of appsWithBundleIds ) { const hasAppBundlesSection = await pageContains( 'App Bundles', `${ process.env.URL }${app.endpoint}` ) if ( !hasAppBundlesSection ) throw new Error(`Couldn't find App Bundles section on ${ app.endpoint }`) } } catch ( error ) { console.log('failed', error) t.fail( error.message ) } t.log( `${appsWithBundleIds.length} valid app pages` ) t.pass() })