💡

Playwrightで実現する『楽楽販売』のDB項目登録自動化

に公開

🎯 はじめに

この記事は、株式会社blueのエンジニアで、主に楽楽販売の運用・開発を担当している西が執筆しました。
社内業務で利用している「楽楽販売」において、DBの項目登録作業が煩雑で時間がかかっていました。GUI操作が中心となるため、作業ミスのリスクも高く、特にExcelをもとに同じ作業を繰り返す業務に課題を感じていました。
この課題を解決するために、Playwrightを用いてブラウザ操作を自動化する仕組みを構築しました。本記事では、その構成とソースコード、今後の改善方針について紹介します。
※ 本記事に掲載するコードは、すべて執筆者自身が提供・作成したもののみです。

👥 どんな人向けの記事?

  • 楽楽販売を業務で利用している方
  • 楽楽販売の管理業務を効率化したい方
  • Playwrightを使ったブラウザ自動化に興味があるエンジニア
  • SaaSの業務自動化を検討している情報システム部門の方

🔧 プロジェクト構成と使用技術

project/
├── index.js
├── package.json
├── src/
│   ├── automation/
│   │   ├── DBSetupAutomation.js
│   │   └── Browser.js
│   ├── utils/
│   │   ├── ExcelReader.js
│   │   └── Logger.js
│   └── config/
│       └── settings.js
└── DB設計書サンプル.xlsx
  • 使用技術:Playwright, ExcelJS, dotenv
  • 処理概要:ExcelからDB項目定義を読み込み → 楽楽販売の管理画面で項目登録を自動実行

📦 パッケージ設定(抜粋)

{
  "name": "db-setup-automation",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "exceljs": "^4.4.0",
    "playwright": "^1.51.1"
  }
}

🔐 環境変数(.env)例

RAKURAKU_URL=https://rakuraku.example.com/login
RAKURAKU_LOGIN_ID=your_login_id
RAKURAKU_PASSWORD=your_password
DB_GROUP_NO=123

💾 index.js

index.js は自動化処理のエントリーポイントです。DB登録処理のクラスをインスタンス化し、run() を非同期で実行しています。

const DBSetupAutomation = require('./src/automation/DBSetupAutomation');

(async () => {
  const automation = new DBSetupAutomation();
  await automation.run();
})();

🧠 Browser.js

Browser.js はすべての自動化処理クラスのベースとして機能する抽象クラスです。 ブラウザの操作やログインに関するメソッドを定義しておき、サブクラスでのオーバーライドを促す設計です。 今後、他の処理(たとえばマスターデータ登録やユーザー管理など)が追加されても共通的に扱えます。

class Browser {
const dotenv = require('dotenv');
dotenv.config();

  async launch() {
        this.browser = await chromium.launch(settings.browser);
        this.page = await this.browser.newPage();
}
    async close() {
        if (this.browser) {
            await this.browser.close();
        }
    }
    async login() {
        await this.page.goto('#url', process.env.RAKURAKU_URL);
        await this.page.fill('#login_id', process.env.RAKURAKU_LOGIN_ID);
        await this.page.fill('#password', process.env.RAKURAKU_PASSWORD);
        await this.page.click('#login_button');
    }
}
module.exports = Browser;

📖 ExcelReader.js

ExcelReader.jsに、Excelファイルからデータを読み込むための機能を定義します。
Excelファイルの読み取りには ExcelJS を使用しています。

    async readRange(filePath, columnLetter, startRow, endRow) {
        try {
            this.logger.info(`ファイルパス: ${filePath}`);
            this.logger.info(`範囲: ${columnLetter}${startRow}${columnLetter}${endRow}`);
            
            const workbook = new ExcelJS.Workbook();
            await workbook.xlsx.readFile(filePath);
            
            const worksheet = workbook.getWorksheet(1);
            
            const values = [];
            for (let row = startRow; row <= endRow; row++) {
                const cell = `${columnLetter}${row}`;
                const cellValue = worksheet.getCell(cell).value;
                if (cellValue) {
                    values.push(cellValue);
                    this.logger.info(`${cell}セルの値: ${cellValue}`);
                }
            }
            return values;
        }
    }

🚀 DBSetupAutomation.js(本処理)

このファイルが自動化処理のメインロジックです。 以下のステップで構成されています:

  1. Excelファイルの読み込み
  2. Playwright を用いてブラウザを起動
  3. .envのログイン情報を用いて楽楽販売へログイン
  4. 対象のDBグループページへ遷移
  5. 新規でDBを追加する
  6. Excelデータを元に、項目名入力→タイプ選択→追加
  7. ブラウザを閉じる

操作対象のUIは CSS セレクタや text ラベルを使って Playwright で選択しています。

const Browser = require('./Browser');
const { chromium } = require('playwright');
const ExcelReader = require('../utils/ExcelReader');

class DBSetupAutomation extends Browser {
  
    async run() {
        await this.loadExcelData();
        await this.launch();
        await this.login();
        await this.navigateToDBSettings();
        await this.createNewDB();
        await this.configureDBFields();
        await this.close();
        return true;
    }

    async loadExcelData() {
        this.tableName = await this.excelReader.readCell(this.excelFilePath, 'B2');
        this.fieldNames = await this.excelReader.readRange(this.excelFilePath, 'C', 2, 22);
        this.dataTypes = await this.excelReader.readRange(this.excelFilePath, 'D', 2, 22);
    }

    async navigateToDBSettings() {
        await this.sideFrame.locator('#nav-db-000').getByRole('link', { name: '[管理]' }).click();
        await this.page.waitForTimeout(settings.timeouts.action);
        
        await this.mainFrame.getByRole('link', { name: 'DB設定' }).nth(this.dbSettingIndex - 1)
            .waitFor({ state: 'visible', timeout: settings.timeouts.element });
        await this.mainFrame.getByRole('link', { name: 'DB設定' }).nth(this.dbSettingIndex - 1).click();
        await this.page.waitForTimeout(settings.timeouts.navigation);
    }

    async createNewDB() {
        await this.mainFrame.getByRole('link', { name: ' DB新規追加' })
            .waitFor({ state: 'visible', timeout: settings.timeouts.element });
        await this.mainFrame.getByRole('link', { name: ' DB新規追加' }).click();
        await this.page.waitForTimeout(settings.timeouts.navigation);
        
        await this.mainFrame.locator('#dbSchemaName')
            .waitFor({ state: 'visible', timeout: settings.timeouts.element });
        await this.mainFrame.locator('#dbSchemaName').clear();
        await this.mainFrame.locator('#dbSchemaName').fill(this.tableName);
        
        await this.mainFrame.getByRole('link', { name: ' 確定' })
            .waitFor({ state: 'visible', timeout: settings.timeouts.element });
        await this.mainFrame.getByRole('link', { name: ' 確定' }).click();
        await this.page.waitForTimeout(settings.timeouts.navigation);
    }

    async configureDBFields() {
        const allButtons = this.mainFrame.locator('a:has-text(" 設定")');
        const allCount = await allButtons.count();
        const targetIdx = allCount - 2;
        await allButtons.nth(targetIdx).click();
        await this.page.waitForTimeout(settings.timeouts.navigation);
        
        await this.mainFrame.getByRole('link', { name: '項目設定' })
            .waitFor({ state: 'visible', timeout: settings.timeouts.element });
        await this.mainFrame.getByRole('link', { name: '項目設定' }).click();
        await this.page.waitForTimeout(settings.timeouts.navigation);
        
        for (let i = 0; i < this.fieldNames.length; i++) {
            await this.addField(this.fieldNames[i], this.dataTypes[i], i + 1);
        }
    }

    async addField(fieldName, dataType, index) {
        await this.mainFrame.locator('#fieldName')
            .waitFor({ state: 'visible', timeout: settings.timeouts.element });
        await this.mainFrame.locator('#fieldName').clear();
        await this.mainFrame.locator('#fieldName').fill(fieldName);
        
        await this.mainFrame.locator('#dataTypeSelect')
            .waitFor({ state: 'visible', timeout: settings.timeouts.element });
        await this.mainFrame.locator('#dataTypeSelect').selectOption({ label: dataType });
        
        await this.mainFrame.getByRole('link', { name: ' 追加' })
            .waitFor({ state: 'visible', timeout: settings.timeouts.element });
        await this.mainFrame.getByRole('link', { name: ' 追加' }).click();
        
        await this.page.waitForTimeout(settings.timeouts.fieldAdd);
    }
}

module.exports = DBSetupAutomation;

📊 Excel定義(DB設計書サンプル.xlsx)構造例

項目名 項目タイプ
企業コード テキスト(1行)
企業名 テキスト(1行)
案件コード キー項目
案件名 テキスト(1行)
案件詳細 テキスト(複数行)
担当者 ユーザ選択肢(1件選択)
商談日付 日時
添付ファイル ファイル
一時金 数値
案件管理コード 自動採番
帳票 自動生成ファイル

🛠 今後の改善ポイント

  • 項目定義フォーマットのJSON/YAML化による汎用性アップ
  • 複数自動化処理への対応に備えた抽象化強化(Browser基底クラス)
  • .envでの設定管理とGit管理除外の徹底

📝 おわりに

Playwrightを活用することで、GUI操作が必要なSaaSでも柔軟に自動化が実現可能であることを確認できました。今後はさらなる自動化対象の拡張、運用環境への組み込みなども視野に入れて進化させていく予定です。

🗨 ご質問・フィードバックはお気軽にコメントください!

株式会社blue TechBlog

Discussion