diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 4218d19712aa0..61297aea12e4e 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -243,6 +243,14 @@ export class CommonPageObject extends FtrService { } = {} ) { let appUrl: string; + + // See https://github.com/elastic/kibana/pull/164376 + if (appName === 'canvas' && !path) { + throw new Error( + 'This causes flaky test failures. Use Canvas page object goToListingPage instead' + ); + } + if (this.config.has(['apps', appName])) { // Legacy applications const appConfig = this.config.get(['apps', appName]); diff --git a/x-pack/plugins/canvas/public/components/home/home.component.tsx b/x-pack/plugins/canvas/public/components/home/home.component.tsx index c29713da70d11..00b018e6cee0e 100644 --- a/x-pack/plugins/canvas/public/components/home/home.component.tsx +++ b/x-pack/plugins/canvas/public/components/home/home.component.tsx @@ -35,6 +35,7 @@ export const Home = ({ activeTab = 'workpads' }: Props) => { { label: strings.getMyWorkpadsTabLabel(), id: 'myWorkpads', + 'data-test-subj': 'workpadListing', isSelected: tab === 'workpads', onClick: () => setTab('workpads'), }, diff --git a/x-pack/test/accessibility/apps/canvas.ts b/x-pack/test/accessibility/apps/canvas.ts index d9508e75bdf27..231bd1c161fde 100644 --- a/x-pack/test/accessibility/apps/canvas.ts +++ b/x-pack/test/accessibility/apps/canvas.ts @@ -12,12 +12,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const esArchiver = getService('esArchiver'); const retry = getService('retry'); - const { common } = getPageObjects(['common']); + const { canvas } = getPageObjects(['canvas']); describe('Canvas Accessibility', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/canvas/default'); - await common.navigateToApp('canvas'); + await canvas.goToListingPage(); }); it('loads workpads', async function () { diff --git a/x-pack/test/functional/apps/canvas/custom_elements.ts b/x-pack/test/functional/apps/canvas/custom_elements.ts index bf9317373b909..7f079666325e3 100644 --- a/x-pack/test/functional/apps/canvas/custom_elements.ts +++ b/x-pack/test/functional/apps/canvas/custom_elements.ts @@ -16,7 +16,7 @@ export default function canvasCustomElementTest({ const testSubjects = getService('testSubjects'); const browser = getService('browser'); const retry = getService('retry'); - const PageObjects = getPageObjects(['canvas', 'common']); + const PageObjects = getPageObjects(['canvas']); const find = getService('find'); const kibanaServer = getService('kibanaServer'); const archive = 'x-pack/test/functional/fixtures/kbn_archiver/canvas/default'; @@ -26,12 +26,9 @@ export default function canvasCustomElementTest({ before(async () => { await kibanaServer.importExport.load(archive); - // open canvas home - await PageObjects.common.navigateToApp('canvas'); // load test workpad - await PageObjects.common.navigateToApp('canvas', { - hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31/page/1', - }); + await PageObjects.canvas.goToListingPage(); + await PageObjects.canvas.loadFirstWorkpad('Test Workpad'); }); after(async () => { diff --git a/x-pack/test/functional/apps/canvas/datasource.ts b/x-pack/test/functional/apps/canvas/datasource.ts index c1cf907bc5342..5afda1a579cd0 100644 --- a/x-pack/test/functional/apps/canvas/datasource.ts +++ b/x-pack/test/functional/apps/canvas/datasource.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function canvasExpressionTest({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['canvas', 'common']); + const PageObjects = getPageObjects(['canvas']); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const monacoEditor = getService('monacoEditor'); @@ -35,7 +35,7 @@ export default function canvasExpressionTest({ getService, getPageObjects }: Ftr }); // create new test workpad - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); await PageObjects.canvas.createNewWorkpad(); }); diff --git a/x-pack/test/functional/apps/canvas/embeddables/lens.ts b/x-pack/test/functional/apps/canvas/embeddables/lens.ts index de7a2eb753204..1e7557cde4c8c 100644 --- a/x-pack/test/functional/apps/canvas/embeddables/lens.ts +++ b/x-pack/test/functional/apps/canvas/embeddables/lens.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function canvasLensTest({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['canvas', 'common', 'header', 'lens']); + const PageObjects = getPageObjects(['canvas', 'header', 'lens']); const esArchiver = getService('esArchiver'); const dashboardAddPanel = getService('dashboardAddPanel'); const dashboardPanelActions = getService('dashboardPanelActions'); @@ -25,7 +25,7 @@ export default function canvasLensTest({ getService, getPageObjects }: FtrProvid await kibanaServer.savedObjects.cleanStandardList(); await kibanaServer.importExport.load(archives.kbn); await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-lens' }); - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); await PageObjects.canvas.createNewWorkpad(); }); @@ -49,9 +49,8 @@ export default function canvasLensTest({ getService, getPageObjects }: FtrProvid it('renders lens visualization using savedLens expression', async () => { // load test workpad - await PageObjects.common.navigateToApp('canvas', { - hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31/page/1', - }); + await PageObjects.canvas.goToListingPage(); + await PageObjects.canvas.loadFirstWorkpad('Test Workpad'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.lens.assertLegacyMetric('Maximum of bytes', '16,788'); diff --git a/x-pack/test/functional/apps/canvas/embeddables/maps.ts b/x-pack/test/functional/apps/canvas/embeddables/maps.ts index 1cdcb644448a8..6cf23726846f1 100644 --- a/x-pack/test/functional/apps/canvas/embeddables/maps.ts +++ b/x-pack/test/functional/apps/canvas/embeddables/maps.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['canvas', 'common', 'header', 'maps']); + const PageObjects = getPageObjects(['canvas', 'header', 'maps']); const dashboardPanelActions = getService('dashboardPanelActions'); const dashboardAddPanel = getService('dashboardAddPanel'); const testSubjects = getService('testSubjects'); @@ -19,7 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { await kibanaServer.savedObjects.cleanStandardList(); // open canvas home - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); // create new workpad await PageObjects.canvas.createNewWorkpad(); await PageObjects.canvas.setWorkpadName('maps tests'); diff --git a/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts b/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts index 6024b4114199a..59285140672c6 100644 --- a/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts +++ b/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['canvas', 'common', 'header', 'discover']); + const PageObjects = getPageObjects(['canvas', 'header', 'discover']); const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); const dashboardAddPanel = getService('dashboardAddPanel'); @@ -21,7 +21,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'test/functional/fixtures/kbn_archiver/dashboard/current/kibana' ); // open canvas home - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); // create new workpad await PageObjects.canvas.createNewWorkpad(); await PageObjects.canvas.setWorkpadName('saved search tests'); @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('edits saved search by-reference embeddable', async () => { await dashboardPanelActions.editPanelByTitle('Rendering Test: saved search'); await PageObjects.discover.saveSearch('Rendering Test: saved search v2'); - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); await PageObjects.canvas.loadFirstWorkpad('saved search tests'); await testSubjects.existOrFail('embeddablePanelHeading-RenderingTest:savedsearchv2'); }); diff --git a/x-pack/test/functional/apps/canvas/embeddables/visualization.ts b/x-pack/test/functional/apps/canvas/embeddables/visualization.ts index 1296bd1e51c8f..40328cbf3890d 100644 --- a/x-pack/test/functional/apps/canvas/embeddables/visualization.ts +++ b/x-pack/test/functional/apps/canvas/embeddables/visualization.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); - const PageObjects = getPageObjects(['canvas', 'common', 'header', 'visualize']); + const PageObjects = getPageObjects(['canvas', 'header', 'visualize']); const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); const dashboardAddPanel = getService('dashboardAddPanel'); @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); // open canvas home - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); // create new workpad await PageObjects.canvas.createNewWorkpad(); await PageObjects.canvas.setWorkpadName('visualization tests'); diff --git a/x-pack/test/functional/apps/canvas/expression.ts b/x-pack/test/functional/apps/canvas/expression.ts index f41bea9774308..7e6c8720ffc43 100644 --- a/x-pack/test/functional/apps/canvas/expression.ts +++ b/x-pack/test/functional/apps/canvas/expression.ts @@ -15,7 +15,7 @@ export default function canvasExpressionTest({ getService, getPageObjects }: Ftr const find = getService('find'); const kibanaServer = getService('kibanaServer'); const monacoEditor = getService('monacoEditor'); - const PageObjects = getPageObjects(['canvas', 'common']); + const PageObjects = getPageObjects(['canvas']); const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -27,9 +27,8 @@ export default function canvasExpressionTest({ getService, getPageObjects }: Ftr await kibanaServer.importExport.load(archive); // load test workpad - await PageObjects.common.navigateToApp('canvas', { - hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31/page/1', - }); + await PageObjects.canvas.goToListingPage(); + await PageObjects.canvas.loadFirstWorkpad('Test Workpad'); }); after(async () => { diff --git a/x-pack/test/functional/apps/canvas/filters.ts b/x-pack/test/functional/apps/canvas/filters.ts index ce8b319b9d53f..d1e85aec2ae87 100644 --- a/x-pack/test/functional/apps/canvas/filters.ts +++ b/x-pack/test/functional/apps/canvas/filters.ts @@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function canvasFiltersTest({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); - const PageObjects = getPageObjects(['canvas', 'common']); + const PageObjects = getPageObjects(['canvas']); const find = getService('find'); const kibanaServer = getService('kibanaServer'); const archive = 'x-pack/test/functional/fixtures/kbn_archiver/canvas/filter'; @@ -24,9 +24,8 @@ export default function canvasFiltersTest({ getService, getPageObjects }: FtrPro before(async () => { await kibanaServer.importExport.load(archive); // load test workpad - await PageObjects.common.navigateToApp('canvas', { - hash: '/workpad/workpad-b5618217-56d2-47fa-b756-1be2306cda68/page/1', - }); + await PageObjects.canvas.goToListingPage(); + await PageObjects.canvas.loadFirstWorkpad('Filter Debug Workpad'); }); after(async () => { diff --git a/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts b/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts index 7577073a1004d..54f42e484734d 100644 --- a/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts +++ b/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts @@ -9,7 +9,7 @@ import path from 'path'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'settings', 'savedObjects']); + const PageObjects = getPageObjects(['settings', 'savedObjects']); describe('migration smoke test', function () { it('imports an 8.2 workpad', async function () { diff --git a/x-pack/test/functional/apps/canvas/reports.ts b/x-pack/test/functional/apps/canvas/reports.ts index 1a1550a62c8eb..a0cb1029f35b3 100644 --- a/x-pack/test/functional/apps/canvas/reports.ts +++ b/x-pack/test/functional/apps/canvas/reports.ts @@ -15,7 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const log = getService('log'); const security = getService('security'); - const PageObjects = getPageObjects(['reporting', 'common', 'canvas']); + const PageObjects = getPageObjects(['reporting', 'canvas']); const archive = 'x-pack/test/functional/fixtures/kbn_archiver/canvas/reports'; describe('Canvas PDF Report Generation', () => { @@ -52,7 +52,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Generating and then comparing reports can take longer than the default 60s timeout this.timeout(180000); - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); await PageObjects.canvas.loadFirstWorkpad('The Very Cool Workpad for PDF Tests'); await PageObjects.reporting.openPdfReportingPanel(); diff --git a/x-pack/test/functional/apps/canvas/saved_object_resolve.ts b/x-pack/test/functional/apps/canvas/saved_object_resolve.ts index d0739c0d2f1b7..c9b4074bc029a 100644 --- a/x-pack/test/functional/apps/canvas/saved_object_resolve.ts +++ b/x-pack/test/functional/apps/canvas/saved_object_resolve.ts @@ -82,10 +82,14 @@ export default function canvasFiltersTest({ getService, getPageObjects }: FtrPro }); it('redirects an alias match', async () => { - await PageObjects.common.navigateToApp('canvas', { - basePath: '/s/custom_space', - hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31-old-id/page/1', - }); + await PageObjects.common.navigateToUrl( + 'canvas', + 'workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31-old-id/page/1', + { + basePath: '/s/custom_space', + shouldUseHashForSubUrl: false, + } + ); // Wait for the redirect toast await retry.try(async () => { @@ -111,10 +115,14 @@ export default function canvasFiltersTest({ getService, getPageObjects }: FtrPro }); it('handles a conflict match', async () => { - await PageObjects.common.navigateToApp('canvas', { - basePath: '/s/custom_space', - hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31-conflict-old/page/1', - }); + await PageObjects.common.navigateToUrl( + 'canvas', + 'workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31-conflict-old/page/1', + { + basePath: '/s/custom_space', + shouldUseHashForSubUrl: false, + } + ); await testSubjects.click('legacy-url-conflict-go-to-other-button'); diff --git a/x-pack/test/functional/apps/canvas/smoke_test.js b/x-pack/test/functional/apps/canvas/smoke_test.js index bdad362d78532..f251e34e8566d 100644 --- a/x-pack/test/functional/apps/canvas/smoke_test.js +++ b/x-pack/test/functional/apps/canvas/smoke_test.js @@ -11,7 +11,7 @@ export default function canvasSmokeTest({ getService, getPageObjects }) { const testSubjects = getService('testSubjects'); const browser = getService('browser'); const retry = getService('retry'); - const PageObjects = getPageObjects(['common']); + const PageObjects = getPageObjects(['canvas']); const kibanaServer = getService('kibanaServer'); const config = getService('config'); const archive = { @@ -31,7 +31,7 @@ export default function canvasSmokeTest({ getService, getPageObjects }) { await kibanaServer.importExport.load(archive.local); } - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); }); after(async () => { diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/canvas.ts b/x-pack/test/functional/apps/maps/group2/embeddable/canvas.ts index d12d1bdce4ecf..8adab9fa86bb4 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/canvas.ts +++ b/x-pack/test/functional/apps/maps/group2/embeddable/canvas.ts @@ -14,9 +14,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('Map embeddable in canvas', () => { before(async () => { - await PageObjects.common.navigateToApp('canvas', { - hash: '/workpad/workpad-c74f9c27-a142-4664-bf8a-69bf782fc268/page/1', - }); + await PageObjects.canvas.goToListingPage(); + await PageObjects.canvas.loadFirstWorkpad('Canvas with map'); }); it('should render map embeddable', async () => { diff --git a/x-pack/test/functional/page_objects/canvas_page.ts b/x-pack/test/functional/page_objects/canvas_page.ts index e2a2c6438d1f3..a075466868bff 100644 --- a/x-pack/test/functional/page_objects/canvas_page.ts +++ b/x-pack/test/functional/page_objects/canvas_page.ts @@ -17,20 +17,36 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo const PageObjects = getPageObjects(['common']); return { + async goToListingPage() { + log.debug('CanvasPage.goToListingPage'); + // disabling the current url check because canvas moved away from + // hash router and redirects from /app/canvas#/ to /app/canvas/ + // but navigateToUrl includes hash in the url which causes test flakiness + await PageObjects.common.navigateToUrl('canvas', '', { + ensureCurrentUrl: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail('workpadListing'); + }, + async enterFullscreen() { + log.debug('CanvasPage.enterFullscreen'); const elem = await find.byCssSelector('[aria-label="View fullscreen"]', 20000); await elem.click(); }, async exitFullscreen() { + log.debug('CanvasPage.exitFullscreen'); await browser.pressKeys(browser.keys.ESCAPE); }, async openExpressionEditor() { + log.debug('CanvasPage.openExpressionEditor'); await testSubjects.click('canvasExpressionEditorButton'); }, async waitForWorkpadElements() { + log.debug('CanvasPage.waitForWorkpadElements'); await testSubjects.findAll('canvasWorkpadPage > canvasWorkpadPageElementContent'); }, @@ -40,6 +56,8 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo * to load the workpad. Resolves once the workpad is in the DOM */ async loadFirstWorkpad(workpadName: string) { + log.debug('CanvasPage.loadFirstWorkpad', workpadName); + await testSubjects.setValue('tableListSearchBox', workpadName); const elem = await testSubjects.find('canvasWorkpadTableWorkpad'); const text = await elem.getVisibleText(); expect(text).to.be(workpadName); @@ -53,6 +71,7 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo }, async fillOutCustomElementForm(name: string, description: string) { + log.debug('CanvasPage.fillOutCustomElementForm', name); // Fill out the custom element form and submit it await testSubjects.setValue('canvasCustomElementForm-name', name, { clearWithKeyboard: true, @@ -65,35 +84,38 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo }, async expectCreateWorkpadButtonEnabled() { + log.debug('CanvasPage.expectCreateWorkpadButtonEnabled'); const button = await testSubjects.find('create-workpad-button', 20000); const disabledAttr = await button.getAttribute('disabled'); expect(disabledAttr).to.be(null); }, async expectCreateWorkpadButtonDisabled() { + log.debug('CanvasPage.expectCreateWorkpadButtonDisabled'); const button = await testSubjects.find('create-workpad-button', 20000); const disabledAttr = await button.getAttribute('disabled'); expect(disabledAttr).to.be('true'); }, async openAddElementMenu() { - log.debug('openAddElementsMenu'); + log.debug('CanvasPage.openAddElementsMenu'); await testSubjects.click('add-element-button'); }, async openAddChartMenu() { - log.debug('openAddChartMenu'); + log.debug('CanvasPage.openAddChartMenu'); await this.openAddElementMenu(); await testSubjects.click('canvasAddElementMenu__Chart'); }, async createNewDatatableElement() { - log.debug('createNewDatatableElement'); + log.debug('CanvasPage.createNewDatatableElement'); await this.openAddChartMenu(); await testSubjects.click('canvasAddElementMenu__table'); }, async openSavedElementsModal() { + log.debug('CanvasPage.openSavedElementsModal'); await testSubjects.click('add-element-button'); await testSubjects.click('saved-elements-menu-option'); @@ -101,14 +123,17 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo }, async closeSavedElementsModal() { + log.debug('CanvasPage.closeSavedElementsModal'); await testSubjects.click('saved-elements-modal-close-button'); }, async expectAddElementButton() { + log.debug('CanvasPage.expectAddElementButton'); await testSubjects.existOrFail('add-element-button'); }, async expectNoAddElementButton() { + log.debug('CanvasPage.expectNoAddElementButton'); // Ensure page is fully loaded first by waiting for the refresh button const refreshPopoverExists = await testSubjects.exists('canvas-refresh-control', { timeout: 20000, @@ -119,6 +144,7 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo }, async getTimeFiltersFromDebug() { + log.debug('CanvasPage.getTimeFiltersFromDebug'); await testSubjects.existOrFail('canvasDebug__content'); const contentElem = await testSubjects.find('canvasDebug__content'); @@ -130,6 +156,7 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo }, async getMatchFiltersFromDebug() { + log.debug('CanvasPage.getMatchFiltersFromDebug'); await testSubjects.existOrFail('canvasDebug__content'); const contentElem = await testSubjects.find('canvasDebug__content'); diff --git a/x-pack/test/reporting_functional/services/scenarios.ts b/x-pack/test/reporting_functional/services/scenarios.ts index f73bd7801b87a..d1123cf2eab05 100644 --- a/x-pack/test/reporting_functional/services/scenarios.ts +++ b/x-pack/test/reporting_functional/services/scenarios.ts @@ -70,7 +70,7 @@ export function createScenarios( const openCanvasWorkpad = async (title: string) => { log.debug(`Opening saved canvas workpad: ${title}`); - await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.goToListingPage(); await PageObjects.canvas.loadFirstWorkpad(title); };